【Vue 3】React 経験者のための Vue 入門 - template・ref・props を React と比較して理解する
React の経験がある方が Vue を学ぶ際、構文の違いに戸惑うことがあります。しかし、Vue と React は根本的な概念が似ており、対応関係を理解すればスムーズに移行できます。
この記事では、Vue 3 の Composition API(<script setup>)を中心に、React との対応表を交えながら基本構文を解説します。
Vue コンポーネントの構造
Vue のコンポーネントは 3 つのブロック で構成されます。
<template>
<!-- HTML構造を記述する場所 -->
<div>
<h1>{{ message }}</h1>
<button @click="count++">クリック: {{ count }}</button>
</div>
</template>
<script setup>
// JavaScriptロジックを記述する場所
import { ref } from 'vue'
const message = ref('こんにちは')
const count = ref(0)
</script>
<style scoped>
/* CSSスタイルを記述する場所 */
h1 {
color: blue;
}
</style>
| ブロック | 役割 | React での対応 |
|---|---|---|
<template> | 何を表示するか(HTML 構造) | JSX(return 文の中身) |
<script> | どう動くか(ロジック・データ) | コンポーネント関数の本体 |
<style> | どう見えるか(装飾) | CSS Modules / styled-components |
template は「テンプレート(ひな形)」の意味で、コンポーネントがレンダリングする HTML の設計図です。React では JSX として JavaScript の中に HTML を書きますが、Vue では HTML 側に専用のディレクティブ(v-*、@、:)を付けるアプローチです。
データ表示とディレクティブ
テキスト表示
Vue ではマスタッシュ構文 {{ }} でデータを表示します。
{"{{ message }}"}
{"{message}"}
ループ(v-for = map)
React の .map() に対応するのが Vue の v-for です。
<!-- Vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
// React
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
条件分岐(v-if = && / 三項演算子)
<!-- Vue -->
<div v-if="show">表示する</div>
<div v-else>非表示</div>
// React
{show ? <div>表示する</div> : <div>非表示</div>}
ディレクティブ対応表
| 機能 | React | Vue |
|---|---|---|
| テキスト表示 | {message} | {{ message }} |
| HTML 挿入 | dangerouslySetInnerHTML | v-html |
| ループ | .map() | v-for |
| 条件表示 | {show && <div>...</div>} | v-if |
| 条件切替 | 三項演算子 | v-if / v-else |
| 非表示(DOM 残す) | 自前で実装 | v-show |
イベントハンドリング
Vue では @ プレフィックスでイベントを記述します。
| 機能 | React | Vue |
|---|---|---|
| クリック | onClick={handler} | @click="handler" |
| 入力変更 | onChange={handler} | @change="handler" |
| フォーム送信 | onSubmit={handler} | @submit="handler" |
| preventDefault | e.preventDefault() | @submit.prevent="handler" |
Vue の イベント修飾子(.prevent、.stop、.once など)を使うと、e.preventDefault() のようなボイラープレートが不要になります。
<!-- Vue: 修飾子で簡潔に書ける -->
<form @submit.prevent="handleSubmit">
<button type="submit">送信</button>
</form>
属性バインディング
動的な属性値には : プレフィックスを使います。
| 機能 | React | Vue |
|---|---|---|
| 動的属性 | src={url} | :src="url" |
| クラス | className={cls} | :class="cls" |
| スタイル | style={{ color: 'red' }} | :style="{ color: 'red' }" |
状態管理: ref と reactive
ref(プリミティブ値向け)
React の useState に相当するのが Vue の ref です。
<script setup>
import { ref } from 'vue'
// React: const [count, setCount] = useState(0)
const count = ref(0)
function increment() {
// script 内では .value が必要
count.value++
}
</script>
<template>
<!-- template 内では .value は不要 -->
<button @click="increment">{{ count }}</button>
</template>
Vue はセッター関数が不要で直接代入できるのが大きな違いです。count.value++ だけで状態が更新され、template が自動的に再レンダリングされます。
reactive(オブジェクト/配列向け)
オブジェクトや配列には reactive も使えます。
<script setup>
import { reactive } from 'vue'
// React: const [form, setForm] = useState({ name: '', age: 0 })
const form = reactive({
name: '',
age: 0,
})
// 直接変更できる(setForm のようなセッター不要)
form.name = '太郎'
form.age = 25
</script>
<template>
<input v-model="form.name" />
<input v-model.number="form.age" />
</template>
ref vs reactive の使い分け
ref | reactive | |
|---|---|---|
| 対象 | プリミティブ値(文字列、数値、真偽値) | オブジェクト・配列 |
| アクセス | count.value(script 内) | form.name(そのまま) |
| 再代入 | count.value = 10 OK | form = {...} NG(参照が切れる) |
迷ったら ref だけ使えば問題ありません。ref はオブジェクトにも使えます。reactive はネストしたオブジェクトを .value なしでアクセスしたい場合に便利です。
状態管理の対応表
| 機能 | React | Vue |
|---|---|---|
| 状態定義 | const [count, setCount] = useState(0) | const count = ref(0) |
| 値の更新 | setCount(count + 1) | count.value++ |
| 算出値 | useMemo(() => ..., [deps]) | computed(() => ...) |
| 副作用 | useEffect(() => ..., [deps]) | watch(source, callback) |
| マウント時 | useEffect(() => ..., []) | onMounted(() => ...) |
双方向バインディング: v-model
Vue 特有の機能として v-model があります。フォーム入力と状態を自動的に同期します。
<input v-model="name" /><input
value={name}
onChange={e => setName(e.target.value)}
/>Props と Emit: コンポーネント間の通信
Props(親 → 子)
<!-- 親コンポーネント -->
<template>
<UserCard name="太郎" :age="25" />
</template>
<!-- 子コンポーネント(UserCard.vue) -->
<script setup>
const props = defineProps({
name: String,
age: Number,
})
</script>
<template>
<div>{{ name }}({{ age }}歳)</div>
</template>
TypeScript を使う場合
<script setup lang="ts">
// 型指定
const props = defineProps<{
name: string
age?: number
}>()
// デフォルト値をつけたい場合
const props = withDefaults(defineProps<{
name: string
age?: number
}>(), {
age: 20,
})
</script>
Emit(子 → 親)
React で「関数を props で渡す」パターンに対応するのが Vue の emit です。
<!-- 親 -->
<template>
<Child @delete="handleDelete" />
</template>
<!-- 子 -->
<script setup>
const emit = defineEmits(['delete'])
function remove(id) {
emit('delete', id)
}
</script>
コンポーネント通信の対応表
| 機能 | React | Vue |
|---|---|---|
| 引数の受け取り | function Comp({ name }) | defineProps({ name: String }) |
| 親への通知 | props.onChange(value) | emit('change', value) |
| children | { children } | <slot /> |
データは props で下へ、イベントは emit で上へ が Vue の基本的なデータフローです。React の「単方向データフロー」と同じ考え方です。
レンダリングの最適化
Vue は自動追跡
Vue はリアクティブな依存関係を自動追跡するため、必要な部分だけが再レンダリングされます。React のように memo や useCallback を手動で指定する必要が少ないのが特徴です。
| 手段 | 説明 |
|---|---|
| コンポーネント分割 | 状態を持つ部分を小さいコンポーネントに分離 |
v-memo | 条件が変わるまで再レンダリングをスキップ |
v-once | 初回のみレンダリング、以降は更新しない |
shallowRef | ネストしたオブジェクトの深い変更を追跡しない |
computed | 依存値が変わった時だけ再計算 |
<!-- v-once: 一度だけ描画(静的コンテンツ向け) -->
<h1 v-once>{{ title }}</h1>
<!-- v-memo: 指定した値が変わるまで更新しない -->
<div v-memo="[itemId]">
{{ heavyCalculation }}
</div>
自動で必要な部分だけ更新。大抵はコンポーネント分割だけで十分
親の再レンダリングで子も全部再レンダリング。React.memo / useMemo / useCallback で手動制御
Options API と Composition API
Vue には 2 つの書き方 があります。
| API | 特徴 | React との類似度 |
|---|---|---|
Composition API(<script setup>) | 関数ベース。Vue 3 推奨 | React Hooks に近い |
Options API(export default {}) | オブジェクトベース。Vue 2 時代の書き方 | クラスコンポーネントに近い |
現在は Composition API が推奨されていますが、既存のプロジェクトでは Options API のコードも見かけます。
// Options API(古い書き方)
export default {
props: ["page"],
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
},
methods: {
greet() {
console.log('Hello')
}
}
}
<!-- Composition API(推奨) -->
<script setup>
import { computed } from 'vue'
const props = defineProps({ page: Object })
const fullName = computed(() => firstName.value + ' ' + lastName.value)
function greet() {
console.log('Hello')
}
</script>
全体の対応まとめ
- JSX → template + ディレクティブ(v-for, v-if, @click, :src)
- useState → ref / reactive
- useMemo → computed
- useEffect → watch / onMounted
- props(関数渡し) → emit
- children → slot
- v-model Vue 独自の双方向バインディング
- イベント修飾子 .prevent, .stop など Vue 独自の省略記法
参考文献
まとめ
React 経験者が Vue を学ぶ際のポイントは以下の 3 点です。
- template + ディレクティブ が React の JSX に対応する。HTML 側に
v-for、v-if、@clickを付けるアプローチ - ref / reactive が
useStateに対応する。セッター関数不要で直接代入できる - props は下へ、emit は上へ が基本のデータフロー。React の「関数を props で渡す」パターンが Vue では
emitになる
Vue はリアクティブシステムが自動追跡するため、React に比べてレンダリング最適化の手動作業が少ないのが特徴です。