こんにちは、かつコーチです。
前回はテンプレート構文の基本として、Mustache構文とディレクティブを解説しました。
今回は少し趣向を変えて、「ReactではこうだったけどVueではどう書くのか」を、記法ごとに対応表で整理していきます。
React編を読んできた方は、「あ、これはあれと同じ役割か」と対応づけながら読み進めてもらえると、Vueの習得スピードがぐっと上がるはずです。
状態管理:useState ⇔ ref
単純な値を管理する
まずは、コンポーネント内で状態を持つ基本の書き方です。
// Reactの書き方
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
<!-- Vueの書き方 -->
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
const increment = () => {
count.value++;
};
</script>
<template>
<div>
<p>カウント:{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
ReactではuseStateが「現在の値」と「更新用の関数」をペアで返しますが、Vueのrefは値そのものを持つオブジェクトを1つ返します。
スクリプト内で値を扱うときはcount.valueのように.valueを付ける必要がありますが、テンプレート内では.valueなしのcountだけで参照できる、という違いに最初は戸惑うかもしれません。
refの詳しい仕組みは次回、reactiveとの違いとあわせて掘り下げます。
副作用処理:useEffect ⇔ watch / onMounted
マウント時の処理
コンポーネントが画面に表示された直後に何か処理をしたい場合の書き方です。
// Reactの書き方
import { useEffect } from "react";
function UserProfile() {
useEffect(() => {
console.log("コンポーネントが表示されました");
}, []); // 空配列=マウント時のみ実行
return <p>プロフィール</p>;
}
<!-- Vueの書き方 -->
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
console.log("コンポーネントが表示されました");
});
</script>
<template>
<p>プロフィール</p>
</template>
ReactのuseEffect(() => {...}, [])(依存配列が空)にあたる処理を、VueではonMountedという専用の関数で表現します。
値の変化を監視する処理
特定の値が変化したときに処理をしたい場合は、ReactのuseEffectに依存配列を指定するパターンと、Vueのwatchが対応します。
// Reactの書き方
import { useEffect, useState } from "react";
function SearchBox() {
const [keyword, setKeyword] = useState("");
useEffect(() => {
console.log(`検索キーワードが変わりました:${keyword}`);
}, [keyword]); // keywordが変わるたびに実行
return <input value={keyword} onChange={(e) => setKeyword(e.target.value)} />;
}
<!-- Vueの書き方 -->
<script setup lang="ts">
import { ref, watch } from "vue";
const keyword = ref("");
watch(keyword, (newValue) => {
console.log(`検索キーワードが変わりました:${newValue}`);
});
</script>
<template>
<input v-model="keyword" />
</template>
Reactでは「依存配列に何を入れるか」で細かく制御していましたが、Vueでは「どのrefを監視するか」をwatchの第一引数に明示する形になり、意図がコードから読み取りやすいと感じる人が多いです。
「マウント時だけの処理」はonMounted、「特定の値の変化に反応する処理」はwatch、という役割分担を覚えておくと迷いません。
Props:親から子へデータを渡す
定義の仕方はよく似ている
コンポーネント間でデータを受け渡す仕組み自体は、ReactとVueでほぼ同じ考え方です。
// Reactの書き方
type UserCardProps = {
name: string;
age: number;
};
function UserCard({ name, age }: UserCardProps) {
return (
<p>
{name}({age}歳)
</p>
);
}
// 使う側
<UserCard name="かつコーチ" age={30} />;
<!-- Vueの書き方(子コンポーネント) -->
<script setup lang="ts">
type UserCardProps = {
name: string;
age: number;
};
defineProps<UserCardProps>();
</script>
<template>
<p>{{ name }}({{ age }}歳)</p>
</template>
<!-- 使う側(親コンポーネント) -->
<template>
<UserCard name="かつコーチ" :age="30" />
</template>
Reactでは関数の引数としてPropsを受け取りますが、VueではdefineProps<型>()という専用の関数(コンパイラマクロ)でPropsの型を宣言します。
「型を定義して、それに沿った値を親から渡す」という考え方自体は完全に共通しているので、TypeScriptで型を書き慣れている人ほど、この対応はすんなり理解できるはずです。
条件分岐・リスト表示:{}構文 ⇔ ディレクティブ
JSXの中の式 vs テンプレートのディレクティブ
前回の記事でも触れましたが、条件分岐とリスト表示の書き方も整理しておきましょう。
// Reactの書き方
function TodoList({ todos, isLoggedIn }: TodoListProps) {
return (
<div>
{isLoggedIn && <p>ログイン中です</p>}
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
);
}
<!-- Vueの書き方 -->
<script setup lang="ts">
type Todo = { id: number; title: string };
defineProps<{ todos: Todo[]; isLoggedIn: boolean }>();
</script>
<template>
<div>
<p v-if="isLoggedIn">ログイン中です</p>
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li>
</ul>
</div>
</template>
Reactは「JavaScriptの式をそのままJSXに埋め込む」という発想で、&&や.map()をそのまま使います。
Vueは「専用のディレクティブをHTML属性として書く」という発想で、v-ifやv-forを使います。
どちらも最終的にやっていることは同じですが、「JavaScriptの中にHTMLを混ぜる」Reactと、「HTMLの中にVue独自の指示を混ぜる」Vue、という発想の違いが根っこにあると捉えると、他の記法の違いも理解しやすくなります。
記法対応表まとめ
一覧で振り返る
ここまでの内容を、対応表として一つにまとめておきます。
| やりたいこと | React | Vue |
|---|---|---|
| 状態を持つ | useState | ref / reactive |
| マウント時の処理 | useEffect(fn, []) | onMounted |
| 値の変化を監視 | useEffect(fn, [dep]) | watch |
| 算出された値 | useMemo | computed |
| Propsの受け取り | 関数の引数 | defineProps<T>() |
| 子から親へのイベント通知 | コールバック関数をProps経由で渡す | defineEmits<T>() |
| 条件分岐 | {condition && <Foo />} | v-if / v-show |
| リスト表示 | array.map() | v-for |
| 属性のバインド | {変数} | v-bind:属性 (:属性) |
| フォームの双方向バインド | value + onChange | v-model |
useMemo⇔computed、defineEmits⇔コールバックPropsについては、それぞれ該当するテーマの記事で改めて実例つきで解説します。
私がこの対応表を最初に作ったときに一番助かったのは、「Vueで新しい概念に出会うたびに、Reactの何に近いかをまず考える」という習慣がついたことです。
完全に一致するわけではありませんが、「ゼロから覚える」より「差分を覚える」方が圧倒的に学習が早く進みます。
まとめ
この記事のポイント
useStateはref、useEffectはマウント時がonMounted・監視がwatchに対応する- Propsの考え方自体は共通で、Vueは
defineProps<T>()で型を宣言する - Reactは「JSの中にHTML」、Vueは「HTMLの中にVue独自の指示」という発想の違いが根っこにある
- 記法対応表を手元に置いておくと、新しい概念に出会うたびに差分で理解できる
- ゼロから覚えるより、Reactとの差分で覚える方が学習効率が良い
次に読むべき記事
対応表の中で触れたrefですが、Vueにはもう一つreactiveというリアクティブな値の作り方があります。
次回は、この2つの違いをしっかり整理していきます。
→ 次の記事:refとreactiveの違い:Vueのリアクティブシステム入門