Vue3におけるデータのやり取り
Vue.js では、親子間、兄弟間、深い階層間など、さまざまな場面で コンポーネント間のデータやイベントのやり取り が求められます。
この章では、まず Vue 3 の代表的なデータのやり取り手段をすべて網羅的に紹介し、それぞれの特徴・方向・用途を比較します。
✅ データのやり取り方法一覧表
方法 | データの流れ | 主な用途例 | 特徴 |
---|---|---|---|
defineProps |
親 → 子 | 表示内容の渡し、初期値 |
readonly 、型定義可能 |
defineEmits |
子 → 親 | ボタン操作、バリデーション結果の通知など | イベント送信、型定義で安全に |
defineModel |
親 ⇄ 子(双方向) |
v-model でフォーム同期 |
props + emits を簡略化、Vue 3.4+ |
slot |
親 → 子 | 親からのテンプレート挿入 | レイアウトや柔軟な表示、再利用性向上 |
スコープ付き slot | 親 ← 子 | 子の値を親に渡して表示変更 |
v-slot="{ data }" 形式でデータ取得 |
provide / inject
|
上位 → 下位 | コンテキストの共有、テーマ、ロケールなど | props のバケツリレー不要、非親子間もOK |
$attrs |
親 → 子(属性継承) | コンポーネントのラップ(例:BaseButton) | 未定義の props や DOM 属性が自動で渡る |
ref (テンプレート) |
親 → 子 | 子のメソッド呼び出しや値取得 |
$refs は非推奨、Composition API の ref() 使用 |
グローバル状態管理(Pinia) | 任意間 ⇄ 任意間 | 複数コンポーネント間の状態共有、API結果保持 | Vue 公式の状態管理ライブラリ、Vuexの後継 |
URL パラメータ / クエリ | ルート ↔ ページ間 | 画面遷移時の ID や状態受け渡し |
useRoute() で取得、router.push() で変更可能 |
カスタムディレクティブ | 親 → 子 | DOM に特定の動作を加える(例:自動フォーカス) |
v-directive 形式、DOM操作を伴う |
イベントバス(非推奨) | 任意間 | グローバルイベント通知(旧手法) | Vue 3 では非推奨。Pinia または emits で代替 |
🧭 使用シーン別のおすすめ手段まとめ
シーン | 推奨される手段 |
---|---|
親 → 子へのデータ渡し |
defineProps 、slot
|
子 → 親への通知 |
defineEmits 、defineModel
|
双方向でデータを同期させたい場合(フォームなど) |
defineModel (Vue 3.4+) |
深い階層に設定値や状態を渡したい |
provide / inject
|
離れたコンポーネント間で状態を共有したい | Pinia |
表示を柔軟にカスタマイズしたい |
slot 、スコープ付きスロット |
画面遷移時に値を受け渡したい | URLパラメータ / クエリ |
子の状態やメソッドを親から操作したい | テンプレート上の ref
|
カスタムUIの属性を透過したい | $attrs |
💡 実務でよくある判断ポイント
判断基準 | 選ぶべき手段例 |
---|---|
明確な親子関係がある |
defineProps , defineEmits
|
離れた位置や兄弟間で共有が必要 |
Pinia , provide/inject
|
コンポーネント再利用性を高めたい |
slot , $attrs
|
コンテキスト(言語・テーマ)を渡したい | provide/inject |
グローバルな状態・API結果を保持したい | Pinia |
双方向のフォーム制御が必要 |
defineModel or v-model + emits |
📎 備考
- Vue 3.4 以降は
defineModel
の導入により、v-model
の記述がより直感的に - スロットや inject は便利だが、依存関係の可視性が落ちるため、適切な設計が重要
- 状態管理が複雑化しやすい場面では Pinia に移行する判断が実務的
📗 defineProps
/ defineEmits
/ defineModel
の実践活用【基本設計とベストプラクティス編】
Vue 3 のコンポーネント開発において最も頻出する3つのデータ連携手段:
defineProps
defineEmits
defineModel
これらの特徴・使い方・設計の勘所を詳しく見ていきましょう。
1️⃣ defineProps
:親から子へ安全に値を渡す
// 子コンポーネント
<script setup lang="ts">
const props = defineProps<{ title: string }>()
</script>
<template>
<h1>{{ props.title }}</h1>
</template>
設計ポイント:
- 型で安全に明示する
- 初期値やデフォルトは基本的に親で持つ
アンチパターン:
- props を直接変更
- 不要な
ref()
包み
2️⃣ defineEmits
:子から親へのイベント通知
// 子コンポーネント
<script setup lang="ts">
const emit = defineEmits<{
(e: 'submit', payload: { name: string }): void
}>()
function handleClick() {
emit('submit', { name: 'Taro' })
}
</script>
設計ポイント:
- イベント名は意味のある動詞に
- payload はオブジェクト型にして柔軟に
アンチパターン:
- 型定義せず emit を使う
- 子が emit のタイミングを複雑化
3️⃣ defineModel
:双方向バインディングを簡潔に
// 子コンポーネント
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
<input v-model="model" />
</template>
// 複数 v-model の例
const title = defineModel<string>('title')
const content = defineModel<string>('content')
設計ポイント:
- Vue 3.4+ で使用可
- v-model の命名に一貫性を持たせる
アンチパターン:
-
defineModel
とdefineProps
を同時に使いデータが二重管理される - v-model 名が不明瞭(例:
v-model="data"
)
🔄 選び方早見表
目的 | 推奨手段 | 補足 |
---|---|---|
一方向データ渡し | defineProps |
readonly。明確な受け渡し |
子 → 親の通知 | defineEmits |
props との責務分離が重要 |
フォーム入力などの同期 | defineModel |
v-model を使うことで直感的になる |
🧾 まとめ:Vue 3におけるデータやり取りの全体像と設計の勘所
Vue 3 の Composition API により、データとイベントの設計がより明示的かつ型安全になりました。
目的に応じた正しい手段を選ぶことで、再利用性と保守性の高い設計が実現します。
✅ 総まとめ
状況 | 最適な手段 |
---|---|
明確な親子関係 |
defineProps , defineEmits
|
双方向バインディングが必要 | defineModel |
非親子間のデータ共有 |
provide/inject , Pinia
|
表示の柔軟性・拡張性が欲しい |
slot , $attrs
|
状態や依存関係が複雑になりそうな場合は Pinia に集約 |
Pinia を検討 |
🎯 実務上のアドバイス
- 責務を明確に: props(入力) / emit(通知) / model(同期)
- バケツリレーを避けたい時は inject/Pinia の検討
- フォーム・再利用部品には
defineModel
が効果的 - v-model と emits の併用は責務の混乱を招くため注意