Props - 親子コンポーネント間の入力契約

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

What(何についてか)

props は、親コンポーネントから子コンポーネントへ値を渡すための仕組みであり、子コンポーネントにとっての外部入力を表す。Vue では、子コンポーネントがどの props を受け取るかを明示的に宣言する。

Why(なぜ必要か)

props を明示宣言することで、コンポーネントの外部インターフェースをコード上で明確にできる。これは関数の引数宣言に近い役割を持つ。

  • 親子間のデータ受け渡しを明示できる
  • どの値が外部から入るかを可視化できる
  • 型や default, validator を通じて入力契約を定義できる
  • 一方向データフローにより状態の所有者を明確に保てる

How(どう動くか)

Props Declaration

<script setup> では defineProps() を用いて props を宣言する。

<script setup lang="ts">
defineProps<{
  title?: string
  likes?: number
}>()
</script>

配列形式やオブジェクト形式でも宣言できるが、TypeScript 利用時は型ベース宣言が自然である。

<script setup>
defineProps({
  title: String,
  likes: Number
})
</script>

Reactive Props Destructure

Vue 3.5+ では、defineProps() の結果を分割代入しても、同一の <script setup> ブロック内で参照される限りコンパイラが props.foo へ補完する。

<script setup>
const { foo } = defineProps(['foo'])
 
watchEffect(() => {
  console.log(foo)
})
</script>

ただし、watch(foo, ...) のように値をそのまま監視対象へ渡すことはできない。リアクティブな参照として扱うには getter で包む必要がある。

watch(() => foo, () => {
  // ...
})

保守性の観点での運用判断

分割代入は便利だが、watch, composable, computed などリアクティビティの境界で挙動差が見えやすい。保守性を優先する場合は、常に props.foo を明示する運用も合理的である。

const props = defineProps<{ foo: string }>()
watch(() => props.foo, () => {
  // ...
})

Prop Passing Details

子コンポーネント側では camelCase で props を宣言し、親テンプレートでは kebab-case で渡すのが標準的。

<script setup>
defineProps({
  greetingMessage: String
})
</script>
<MyComponent greeting-message="hello" />

値の受け渡しでは、文字列リテラル以外は v-bind:)を使うのが基本になる。

<MyComponent title="hello" />
<MyComponent :likes="42" />
<MyComponent :disabled="false" />
<MyComponent :items="items" />

固定文字列を : 付きで渡す場合は JavaScript 式として文字列化する必要がある。

<MyComponent :title="'hello'" />

One-Way Data Flow

props は親から子への一方向データフローを形成する。子コンポーネントで props を直接変更してはいけない。

const props = defineProps(['foo'])
 
// props.foo = 'bar' // NG

子側で値を加工したい場合は、用途に応じてローカル state か computed を使う。

const props = defineProps<{ initialCounter: number; size: string }>()
 
const counter = ref(props.initialCounter)
const normalizedSize = computed(() => props.size.trim().toLowerCase())

実務上の判断軸

flowchart TD
  A["子で props を使いたい"] --> B{"用途は何か"}
  B -->|"そのまま参照"| C["props.foo を読む"]
  B -->|"初期値として使う"| D["ref(props.foo) でローカル化"]
  B -->|"加工して使う"| E["computed で派生値を作る"]
  B -->|"親へ変更を返したい"| F["emit または v-model を検討"]

Prop Validation

props には型、必須性、デフォルト値、独自バリデーションを付与できる。

<script setup>
defineProps({
  title: {
    type: String,
    required: true
  },
  likes: {
    type: Number,
    default: 0
  },
  status: {
    type: String,
    validator: (value) => ['success', 'warning', 'danger'].includes(value)
  }
})
</script>

配列やオブジェクトの default は factory function で返す。

items: {
  type: Array,
  default: () => []
}

Zod との役割分担

Zod は props 宣言そのものの置き換えではなく、外部データや複雑なオブジェクト構造の厳密検証に向く。props は Vue のコンポーネント機構に統合された入力契約であり、required, default, Boolean casting などの役割を持つ。

  • props は Vue の公式インターフェース定義
  • Zod は値の中身を精密検証する補強材
  • 単純 props は Vue 標準で十分
  • 外部入力やネストの深い構造には Zod 併用が有効

Options API → Composition API 差分

項目Options API(旧)Composition API(新)
props宣言props: { ... } を options に記述defineProps() で宣言する
props参照this.fooprops.foo または destructure
型付けTypeScript統合はやや間接的<script setup lang="ts"> で自然に記述できる
入力契約の見え方options 内にまとまるsetup 冒頭で明示できる

Key Concepts

用語説明
Props親から子へ渡される、子コンポーネントの外部入力
defineProps()<script setup> で props を宣言するためのマクロ
One-Way Data Flow親から子へだけ値が流れる設計原則
Prop Validation型、required、default、validator による入力契約定義
Reactive Props DestructuredefineProps() の分割代入を Vue 3.5+ でリアクティブに扱う仕組み
v-bind / :値を JavaScript 式として props に渡す記法
ZodVue props の代替ではなく、外部データ検証を補強するスキーマライブラリ