Component Basics - 親子コンポーネント通信の基礎

ロードマップ: Vue.js学習ロードマップ

What(何についてか)

Component Basics は、Vue におけるコンポーネントの役割と、親子コンポーネント間の基本的な通信パターンを扱う。コンポーネントは UI とロジックをまとまりとして分割する単位であり、アプリ全体はそれらの入れ子構造として組み立てられる。

この章では、コンポーネントの定義、親から子への props、子から親への custom events、可変な UI 断片を渡す slots、表示コンポーネント自体を切り替える dynamic components を一通り押さえる。

Why(なぜ必要か)

画面全体を 1 つの巨大テンプレートとして扱うと、再利用性も保守性も急激に落ちる。コンポーネントへ分割することで、以下の利点が得られる。

  • UI を責務ごとに分割できる
  • 同じ見た目や振る舞いを複数箇所で再利用できる
  • 親がデータを持ち、子が表示や局所ロジックを担当する構造を作れる
  • 通信ルールを明示することで、データフローを追いやすくできる

特に Vue では、親から子への一方向データフローを props で表現し、逆方向の通知を emit で返す構造が基本になる。これにより、データの所有者と更新責任を明確に保てる。

How(どう動くか)

コンポーネントの定義

Vue では通常、1 コンポーネントを 1 つの .vue ファイルとして定義する。Single-File Component(SFC)では、<script setup> にロジック、<template> に UI を記述する。

<script setup>
import { ref } from 'vue'
 
const count = ref(0)
</script>
 
<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

Vue アプリはこうしたコンポーネントを木構造で組み合わせて作る。ページ全体は 1 つの巨大 UI ではなく、独立した部品の集合として扱う。

コンポーネントの利用

親コンポーネントは子コンポーネントを import して、テンプレート内でタグとして使う。

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
 
<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

<script setup> では import したコンポーネントが自動でテンプレートから使える。複数回使えば、そのたびに独立したインスタンスが生成され、それぞれが別の state を持つ。

Props による親 → 子通信

親から子へ値を渡す時は props を使う。子コンポーネントは defineProps() で受け取る属性を宣言する。

<script setup>
defineProps(['title'])
</script>
 
<template>
  <h4>{{ title }}</h4>
</template>

親は属性として値を渡す。

<BlogPost title="My journey with Vue" />

動的な値を渡す時は v-bind を使う。

<BlogPost :title="post.title" />

props の本質は、親が持つデータを子へ入力として流す一方向データフローにある。子は親の state を直接変更せず、受け取った値を表示や処理に使う。

Custom Events による子 → 親通知

子から親へ「こうしてほしい」と通知する時は custom events を使う。親は @event-name で受け取り、子は $emit() で通知する。

<BlogPost
  :title="post.title"
  @enlarge-text="postFontSize += 0.1"
/>
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

<script setup> では emit するイベントを defineEmits() で宣言できる。

<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

これにより、親が state を所有し、子は通知だけを返す構造が保たれる。

Slots による UI 断片の注入

props が値を渡す仕組みであるのに対し、slot は親が子へ UI 断片そのものを差し込む仕組みである。

<template>
  <button class="fancy-btn">
    <slot></slot>
  </button>
</template>
<FancyButton>
  Save
</FancyButton>

slot は柔軟性が高いが、コンポーネントの責務を広げやすい。そのため、形が決まっていて値だけ渡せば足りる場合は props を優先し、可変なテンプレート構造が必要な時だけ slot を使う。

通常の slot 利用は即座に XSS リスクになるわけではない。注意すべきなのは、生 HTML 文字列を直接差し込む v-html のようなケースである。

Dynamic Components

表示するコンポーネント自体を切り替える時は、組み込みの <component>:is を使う。

<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'
 
const currentTab = ref('Home')
 
const tabs = {
  Home,
  Posts,
  Archive
}
</script>
 
<template>
  <button
    v-for="(_, tab) in tabs"
    :key="tab"
    @click="currentTab = tab"
  >
    {{ tab }}
  </button>
 
  <component :is="tabs[currentTab]" class="tab" />
</template>

このパターンでは、状態としてはタブ名の文字列を持ち、描画時に「タブ名 → コンポーネント定義」の対応表から実体を引く。タブ UI のように、状態管理は軽く保ちつつ表示対象だけ差し替えたい時に有効である。

切り替え時には、表示を外れたコンポーネントは unmount され、新しいコンポーネントが mount される。ローカル state を保持したまま切り替えたい場合は KeepAlive を使う。

<KeepAlive>
  <component :is="tabs[currentTab]" />
</KeepAlive>

in-DOM template の注意点

SFC ではなく HTML 内に直接 Vue テンプレートを書く in-DOM template では、ブラウザの HTML パース規則の影響を受ける。

  • コンポーネント名は kebab-case を使う
  • 自己終了タグではなく明示的な閉じタグを書く
  • <table><ul> など配置制約のある要素内では注意する

ただし、通常の .vue ベース開発ではこれらを強く意識する場面は少ない。SFC を前提にする限り、優先度は低い知識である。

Options API → Composition API 差分(該当箇所のみ、補足として)

項目Options API(旧)Composition API(新)
子コンポーネント登録components: { ButtonCounter }<script setup> で import するだけ
props 宣言props: ['title']defineProps(['title'])
emits 宣言emits: ['enlarge-text']defineEmits(['enlarge-text'])
state 参照this.title, this.postFontSize変数参照ベース、this 不要
dynamic component<component :is="currentTab">同様だが <script setup> と import ベースで記述しやすい

Key Concepts

用語説明
componentUI とロジックを独立単位として切り出した再利用可能な部品
SFC.vue ファイルで script / template / style をまとめて定義する形式
props親から子へ渡す入力データ
emit子から親へ返す通知イベント
slot親が子へ差し込む可変な UI 断片
dynamic component<component :is="..."> で表示対象を切り替える仕組み
KeepAlive切り替えで非表示になったコンポーネントを破棄せず保持する built-in component

実務での判断軸

  • まず UI を責務ごとにコンポーネントへ分割する
  • 値を渡すなら props を使う
  • 変更要求や通知を返すなら emit を使う
  • 形が決まっているなら props を優先し、可変な UI 構造が必要な時だけ slot を使う
  • 表示部品そのものを切り替える時だけ dynamic components を使う
  • .vue ベース開発を前提にし、in-DOM template の制約は必要になった時だけ参照する

通信フローの全体像

flowchart LR
  A["Parent state"] -->|props| B["Child component"]
  B -->|emit| A
  A -->|slot content| B
  A -->|:is で切替| C["Rendered child type"]