なぜこの記事を書こうと思ったのか
基本的なところは似ているとかなんとかのReactとVue3...
今後扱う場面が出てくることになったのでVueってこう書くのねって言う点をまとめていきたいと思います。
書き方は完全に忘備録です、ただ、おおよそReactの経験者の方から雑な書き方ながらも理解しやすいように書いてみたいなぁと思い今回記事にしました。
ファイルの拡張子
Reactで扱っていたのがいわゆるJSX型と .ts or .js
- .js
- .jsx
- .ts
- .tsx
TypeScriptを使うか使わないかで t となるか j となるかということでした。基本的に扱っていたのはこのパターン。
ではVueではどうなるのか??
- .vue
- .ts(utils関数や定数などをTypeScriptで使用するケース)
- .js (TypeScript未使用時)
TypeScriptを使おうが使うまいが、どちらにしても.vueが拡張子らしい....
utils関数なんかを格納する場合は ts や js でいいみたい。これだけですでにとっつきづらい....
.vueファイルの役割ってなんなのか?
「単一ファイルコンポーネント(SFC)で、HTML, CSS, JavaScript 」を一つのファイルで管理する役割を持っている
役割としては三項目
- HTML部分を構成する「template」
- コンポーネント動作(状態管理・関数・ライフサイクルを定義する)「script」
- コンポーネントのstyleを定義する「style」
コンポーネントとしての独立したUIを生成し、再利用可能にする。
ざっくりと調べた内容としてまずはまあコンポーネント化に関すること(再利用とか)はあんまりReactと差異は感じないかなぁと言う印象。
ただ、厳密に決まっていないとはいえ疑問に残ったのは書き込む順番.....
Reactでの実装時はコンポーネントディレクトリ内部に別途style.tsとしてコンポーネント用のCSSファイルを用意。TSX形式ではロジックを書いた後にreturnでhtml要素のような書き方が自分的な常識.....
tailwindとか使えばstyleエリアは実質消えてるのでそこまで大きな問題でも無いような気がしないでもない。
ちなみに「先にhtml要素書いてからロジックの関数周りを書いてしまうとファイル読み込む時のホイスティング的な問題は....?」というのを感じて調べてみたところ、.Vueファイル上ではvueコンポーネントとしてsetupなどのライフサイクルで処理されるので、javasciptの通常のホイスティングとは問題が無いようだ。
ただ個人的にはロジック側を先に書きたいなぁという印象です、(現場に合わせてなれるしかないか....)
と思っていたら案外ロジックから書きたい派の方もいらっしゃるようです。なるべくこっちを個人的には使いたいかなぁ。
<template>
<button @click="handleClick">クリック</button>
</template>
<script lang="ts"> // TypeScriptで書くで〜の宣言
import { ref } from "vue";
export default {
setup() {
const count = ref(0); //(useState的な)
const handleClick = () => {
count.value++; // useStateのset的な
console.log("クリックされた!", count.value);
};
return { handleClick };
},
};
</script>
vue3では基本的にscript側でのロジック処理は、setUp()としてラップして関数処理の中で書くようだ。
@clickってなんなん、onClickじゃだめなのと思ったので調べました、基本的にBluerの時なんかでもonBluerとReactでは書いてましたが、onは省略して「@blur」などと書くみたい、深掘りしすぎるとわけわからなくなるので。vueはなんかそう言うものだと思っておきます。
疑問はありますが比較的Reactのカスタムフックを使う方式に近そうな感じですね。外部exportこうやるのかって言う点はわかりました。
vueではrefとreactiveが、useState的な使い方をされる
「プリミティブ(数値・文字列・真偽値)」なら ref を使う!
<template>
<input type="text" @change="handleChange" />
</template>
<script lang="ts">
import { ref } from "vue";
export default {
setup() {
const inputValue = ref("");
const handleChange = (event: Event) => {
const target = event.target as HTMLInputElement;
inputValue.value = target.value;
};
return { inputValue, handleChange };
},
};
</script>
ちなみに入力をするケースでのイベントの型定義として自分の中の認識では「"changeEvent ,HTMLInputElement」だったのですがこれはReact独自の書き方なので、vueでは扱えないと。
なのでasを用いて型定義を用いるようです。あまりasは使いたくない派だったのでやや腑に落ちない感触ですが、「vueはこういうもの」だと思っておけば問題ないでしょう。
実は過去に経験した現場で、React未経験者でvueの経験ありという方が参画された際、やけに as 使うなぁと思っていたのですがこういう経験から来てるのかもしれません。
asの扱い方というか許容範囲的なものを知るのにはvueでの経験を踏まえるのがいいのかなぁと感じました。
「オブジェクト or 配列」なら reactive を使う
reactive は React の useState でスプレッド構文 (...) を使うケースに近い!
<template>
<p>名前: {{ user.name }}</p>
<button @click="changeName">名前変更</button>
</template>
<script lang="ts">
import { reactive } from "vue";
export default {
setup() {
const user = reactive({ name: "やまだ" });
const changeName = () => {
user.name = "たなか"; // ✅ プロパティを変更すると Vue が再レンダリング
};
return { user, changeName };
},
};
</script>
reactiveに対しては直接破壊的メソッドを使用することも可能なようだ.... Reactではスプレッド構文で配列をコピーして操作->setとする流れだったのでまぁまぁ驚いたところ。
とはいえレビュワーとしてはどういうふうに許容するんだろうかと。やや疑問が残るところではあった。
また、vueファイル内でコンポーネントの ref か reactive が更新された際に レンダリングが発生して画面が更新されるという点もほぼ同様の模様。
カスタムフック的な書き方は?
Reactで割とよく使われるんじゃないでしょうか、「useInput」です。これをvueで使用するにはどうするのか?
import { useState } from "react";
export const useInput = (initialValue: string) => {
const [value, setValue] = useState(initialValue);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
return { value, onChange: handleChange };
};
Vue では composable を作成することで、カスタムフックのような形にできるようです。
// composables/useInput.ts
import { ref } from "vue";
export const useInput = (initialValue = "") => {
const value = ref(initialValue);
const onChange = (event: Event) => {
value.value = (event.target as HTMLInputElement).value;
};
return { value, onChange };
};
書き方としてはほぼ同じでした、(ちょっとびっくり)
ちなみにファイル名称としてもuse ~ の形式で問題ない模様。
ここはそんなに難儀しなそうな感じはあります。なんとなくhooksのほうが読みやすいなぁぐらいでしょうか。
vueでのuseEffect的なものは?
コンポーネントのマウント、アンマウントっていう考え方は共通のようなのだけれど、単純にvueではuseEffectは使えないので書き方としては
Reactでは第二引数の依存配列を空にすることで初回マウント時にイベントを発火させることができていました。
returnを記述することでアンマウント処理が実行される、よくある構文です。
import { useEffect } from "react";
const Component = () => {
useEffect(() => {
console.log("React: コンポーネントがマウントされた!");
return () => {
console.log("React: コンポーネントがアンマウントされた!");
};
}, []);
return <div>React コンポーネント</div>;
};
vueではonMounted()とonUnmounted()を使用する
<script lang="ts">
import { onMounted, onUnmounted } from "vue";
export default {
setup() {
onMounted(() => {
console.log("Vue: コンポーネントがマウントされた!");
});
onUnmounted(() => {
console.log("Vue: コンポーネントがアンマウントされた!");
});
return {};
},
};
</script>
基本的にこれも概念というか、使い方としては同様の認識で問題が無いようで、イベントリスナーのクリーンアップ関数なんかも同様の書き方をすれば問題なさそう。また、いずれも複数回使用しても問題はないみたい。
繰り返し表示の方法について
Reactではmapを使用して配列要素を繰り返し表示するなどしていましたが、
Vueではそれが異なり、v-forを使用してコンポーネント内で繰り返し表示を書くことが可能。
<template>
<UserCard v-for="user in users" :key="user.id" :user="user" />
</template>
<script lang="ts">
import { ref } from "vue";
import UserCard from "@/components/UserCard.vue";
export default {
setup() {
const users = ref([
{ id: 1, name: "やまだ", age: 30 },
{ id: 2, name: "たなか", age: 25 },
{ id: 3, name: "すずき", age: 40 },
]);
return { users };
},
};
</script>
あれ?この場合は配列を管理しているからreactiveでは?と思ったので再度調査しました。
reactiveを使用する場合は.valueをチェーンする必要があるとのことで、reactiveの場合は以下のように書く必要があるみたい。
import { reactive, toRefs } from "vue";
const users = reactive([
{ id: 1, name: "やまだ", age: 30 },
{ id: 2, name: "たなか", age: 25 },
{ id: 3, name: "すずき", age: 40 },
]);
return toRefs({ users }); // `reactive([])` を return するなら `toRefs()` が必要!
- ref([])をreturnする場合 ⇨ .valueは不要で "UserCard v-for="user in users"
- reactive([])をreturnする場合 → .valueが必要で "UserCard v-for="user in users.value"
- reactive({ users: [] }) + toRefs()とする場合 → .valueは不要で、 "UserCard v-for="user in users" のように書ける
随所で色々なパターンが出てきますね.... 概念がよくわからん。
ここに関してはuseStateで一個で管理できた、Reactの方が優秀な感じがあります(とっつきづらい)
他にもある 「v- ~~」について
- v-if ⇨ 条件によって要素を表示非表示
<p v-if="isVisible">このテキストは表示される</p>
<button @click="isVisible = !isVisible">切り替え</button>
- v-show ⇨ 条件によって要素を表示非表示(display:none)
<p v-show="isVisible">このテキストは display: none で隠れる</p>
<button @click="isVisible = !isVisible">切り替え</button>
- v-model ⇨ 双方向バインディング(フォーム入力)
<input v-model="name" />
<p>入力された名前: {{ name }}</p>
Propsの扱い方ってどうするの?
reactで親コンポーネントからこコンポーネントに値を引き渡す際はFCとしていたが、
vueではdefineProps()を使用する
<template>
<div>
<h2>{{ user.name }} ({{ user.age }}歳)</h2>
</div>
</template>
<script lang="ts">
import { defineProps } from "vue";
export default {
setup() {
const props = defineProps<{
user: { id: number; name: string; age: number };
}>();
return { ...props }; // こうすることで template 側で直接 `user` を参照できる!
},
};
</script>
また、Vue はテンプレートの記述をシンプルにするために、「親 → 子のデータの流れは props、子 → 親は emit」という 明確なルールを作っている。
ここに関してはちょっとReactとの違いが割とあるため、ちょっと難航しそうなので別途個人的に学習してみたいと思います。
最後に
完全忘備録でかなり雑な書き方となってしまいましたがおおよそこんなものかという点は掴めたかなという印象....
やはり新しいフレームワークの学習をしようと思うと、なんとなくわずらわしい感じとか違和感とか色々ありますね。
今後バックエンド側の知見も深めていきたいので、一旦ここまでにして自主制作作業に取り組んでみようと思います。
ありがとうございました。