【Vue】Propsの定義と受け取り方

JavaScript

こんにちは、かつコーチです。

前回は watchwatchEffect の使い分けを解説しました。

今回は、コンポーネント同士でデータをやり取りするための基本、「Props(プロップス)」を扱います。

Vueでの開発は、画面を小さなコンポーネントに分けて組み合わせていくスタイルが基本です。

そのときに欠かせないのが、親から子へデータを渡す仕組みであるPropsです。

Propsとは?

なぜPropsが必要なのか

Props とは、親コンポーネントから子コンポーネントへ渡すデータのことです。

たとえば「ユーザーカード」を表示するコンポーネントを考えてみましょう。

名前や年齢が違う複数のユーザーを表示したい場合、コンポーネントの中身は同じでも、表示するデータだけを変えたいはずです。

このとき、親から「このデータを使って表示して」と渡す役割を果たすのがPropsです。

コンポーネントを再利用可能な「型」として設計し、中身のデータだけを外から差し替える、という考え方だとイメージすると分かりやすいです。

親から子への一方通行

Propsの重要な性質として、データは親から子への一方通行という点があります。

子コンポーネント側でPropsの値を直接書き換えることはできません。

「子から親にデータを伝えたい」という逆方向の通信には、次回解説する emit という別の仕組みを使います。

まずは「Propsは親から子への一方通行」というルールをしっかり覚えておきましょう。

Propsの基本の書き方

子コンポーネント側で受け取る

子コンポーネント(UserCard.vue を想定)では、defineProps を使ってPropsを定義します。

<script setup lang="ts">
interface Props {
  name: string
  age: number
}

const props = defineProps<Props>()
</script>

<template>
  <div class="user-card">
    <p>{{ props.name }}さん({{ props.age }}歳)</p>
  </div>
</template>

<script setup> では interface で型を定義し、defineProps<Props>() とすることで、TypeScriptの型チェックが効いたPropsを受け取れます。

defineProps はimport不要で使える特別な関数(コンパイラマクロ)なので、そのまま使って問題ありません。

親コンポーネント側で渡す

親コンポーネント側では、属性のようにPropsを渡します。

<script setup lang="ts">
import UserCard from './UserCard.vue'
</script>

<template>
  <UserCard name="かつコーチ" :age="30" />
</template>

文字列をそのまま渡す場合は name="かつコーチ" のように書けますが、数値や変数、オブジェクトを渡す場合は :age="30" のようにコロン(v-bind の省略形)を付ける必要があります。

私が初心者の頃、この「コロンを付ける・付けない」の判断でよくつまずいていました。

つまずきやすいポイント:コロンの付け忘れ

数値が文字列として渡ってしまう

❌ Before:コロンを付けずに渡してしまう

<template>
  <UserCard name="かつコーチ" age="30" />
</template>
<script setup lang="ts">
interface Props {
  name: string
  age: number
}

const props = defineProps<Props>()
// props.age は本来number型のはずが、実際には文字列の"30"として渡ってきてしまう
</script>

このコードは一見動いているように見えますが、age にはコロンを付けていないため、常に文字列("30")として渡されてしまいます。

TypeScriptで型チェックが効いていれば警告に気づけますが、そうでない場合は「なぜ計算結果がおかしいのか」と気づきにくいバグにつながります。

私自身、年齢に1を足す処理を書いたときに "30"+1"301" になってしまい、しばらく原因が分からなかった経験があります。

✅ After:コロンを付けて動的な値として渡す

<template>
  <UserCard name="かつコーチ" :age="30" />
</template>

コロンを付けることで、30 はJavaScriptの式として評価され、正しくnumber型の値として渡されます。

「固定の文字列を渡すとき以外は、基本的にコロンを付ける」と覚えておくと、この手の事故を防げます。

Propsに初期値や制約を付ける

デフォルト値を設定する

Propsが渡されなかった場合の初期値を設定したいときは withDefaults を使います。

<script setup lang="ts">
interface Props {
  name: string
  age?: number
}

const props = withDefaults(defineProps<Props>(), {
  age: 20,
})
</script>

<template>
  <p>{{ props.name }}さん({{ props.age }}歳)</p>
</template>

age?: number のように ? を付けて任意(オプショナル)にしたうえで、withDefaults の第2引数にデフォルト値を指定します。

こうしておくと、親コンポーネントが age を渡さなかった場合でも 20 が使われるため、undefined によるエラーを防げます。

分割代入で受け取るときの注意

Vue 3.5以降では、defineProps の戻り値を分割代入で受け取ることもできます。

<script setup lang="ts">
interface Props {
  name: string
  age?: number
}

const { name, age = 18 } = defineProps<Props>()
</script>

<template>
  <p>{{ name }}さん({{ age }}歳)</p>
</template>

分割代入でもデフォルト値を指定できるため、withDefaults を書かずに済むケースが増えます。

どちらの書き方も現場で見かけるので、両方を知っておくとコードリーディングに困りません。

まとめ

この記事のポイント

  • Propsは親コンポーネントから子コンポーネントへデータを渡す仕組みで、一方通行である
  • 子コンポーネント側では defineProps<Props>() で型付きのPropsを受け取る
  • 動的な値を渡すときはコロン(:)を付け忘れると、文字列として渡ってしまう
  • withDefaults や分割代入でPropsにデフォルト値を設定できる

次に読むべき記事

次回は、Propsとは逆方向、子コンポーネントから親コンポーネントへイベントを伝える emit を解説します。

→ 次の記事:emitで子から親にイベントを伝える

タイトルとURLをコピーしました