[Vue入門] PropsとdefineProps 

このページは、すでにコンポーネントの基礎を読んでいることを前提にしています。初めてコンポーネントに触れる方は、まずこちらをお読みください。

プロパティの宣言

Vue のコンポーネントでは、明示的な props (プロパティ) の宣言が必要です。これにより Vue は、外部からコンポーネントに渡された props を fallthrough 属性 (次のセクションで説明します) として扱うべきかを知ることができます。

プロパティは、以下のように props オプションを使って宣言します:

<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

//<script setup>を使わないやり方↓
export default {
  props: ['foo'],
  created() {
    // props は `this` 上で公開されます。
    console.log(this.foo)
  }
}

プロパティの宣言には、文字列の配列に加え、オブジェクト構文を用いることもできます:

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

オブジェクト宣言の構文に含める各プロパティについて、キーにはプロパティの名前、値には目的の型のコンストラクター関数を指定します。

これは自分のコンポーネントを文書化するのに役立ちます。また、誤った型を渡した時にブラウザーのコンソールに警告が表示されるようになり、コンポーネントを利用する他の開発者のためにもなります。このページの後半では、プロパティのバリデーションについて詳しく説明します。

コンポーネントのプロパティの型付けも合わせて参照してください。

プロパティ渡しの詳細

プロパティ名での大文字・小文字の使い分け

長いプロパティ名は、camelCase (キャメルケース) で宣言します。そうすると、プロパティのキーとして使うときに引用符を使わなくて済みます。camelCase は JavaScript の有効な識別子であるため、以下のようにテンプレート式で直接参照することができます:

defineProps({
  greetingMessage: String
})

//<script setup>を使わないやり方↓
export default {
  props: {
    greetingMessage: String
  }
}
<span>{{ greetingMessage }}</span>

技術的には、子コンポーネントにプロパティを渡すときにも camelCase を用いることができます (ただし DOM テンプレート内を除く)。しかし、常に kebab-case (ケバブケース) を用いて HTML の属性に揃える、以下のような表記が慣例となっています:

<MyComponent greeting-message="hello" />

コンポーネントのタグには、可能な限り PascalCase を用いることが推奨されます。これは Vue コンポーネントとネイティブ要素の区別が付き、テンプレートの可読性が高まるためです。しかし、プロパティを渡すときに camelCase を用いることには、それほど実用的なメリットがありません。そのため、Vue では各言語の規約に従うことが推奨されます。

静的なプロパティと動的なプロパティ

ここまでで、静的な値として渡すプロパティを見てきました。例:

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

v-bind またはそのショートカットである : を使って、プロパティを動的に割り当てる例も見てきました。例:

<!-- 変数の値を動的に代入 -->
<BlogPost :title="post.title" />

<!-- 複雑な式の値を動的に代入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

いろいろな種類の値を渡す

上の 2 つは、たまたま文字列の値を渡す例ですが、プロパティには どんな 種類の値も渡すことができます。

数値

<!-- `42` は静的な値ですが、これが文字列ではなく JavaScript の        -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :likes="42" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :likes="post.likes" />

ブール値

<!-- 値なしでプロパティを指定すると、暗黙で `true` を指定したことになります。 -->
<BlogPost is-published />

<!-- `false` は静的な値ですが、これが文字列ではなく JavaScript の     -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :is-published="false" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :is-published="post.isPublished" />

配列

<!-- 静的な配列でも、これが文字列ではなく JavaScript の         -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :comment-ids="post.commentIds" />

オブジェクト

<!-- 静的なオブジェクトでも、これが文字列ではなく JavaScript の -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :author="post.author" />

オブジェクトを利用した複数のプロパティのバインディング

オブジェクトに含まれるすべてのプロパティを props として渡したい場合には、引数なしの v-bind を使用します (:プロパティ名 の代わりに v-bind)。例えば、以下のような post オブジェクトがあるとします:

const post = {
  id: 1,
  title: 'My Journey with Vue'
}

//<script setup>を使わないやり方↓
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}

このとき、テンプレートでは以下を用いることができます:

<BlogPost v-bind="post" />

これは以下の表記と等価です:

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

一方向のデータフロー

すべてのプロパティでは、子のプロパティと親のプロパティとの間に一方向バインディングが形成されます。親のプロパティが更新されたときには子にも流れますが、その逆はありません。これにより、親の状態が誤って子コンポーネントによって変更されてアプリのデータフローが把握しにくくなる、といった事態が防がれます。

さらに、親コンポーネントが更新されるたびに、子コンポーネント内のすべてのプロパティは最新の値に更新されます。そのため、子コンポーネント内でプロパティの変更を試みてはいけません。もし試みると、Vue がコンソールで警告を発します:

const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'


//<script setup>を使わないやり方↓
export default {
  props: ['foo'],
  created() {
    // ❌ 警告、プロパティは読み取り専用です!
    this.foo = 'bar'
  }
}

通常、プロパティを変更したい状況には以下の 2 つがあります:

  • プロパティは初期値を渡すために用いて、それ以降、子コンポーネントではローカルのデータプロパティとして利用したい。 この場合、以下のようにローカルのデータプロパティを定義して、その初期値にプロパティを使用するのが最も適切です:export default { props: ['initialCounter'], data() { return { // this.initialCounter は counter の初期値を指定するためだけに // 使われ、今後発生するプロパティの更新からは切り離されます。 counter: this.initialCounter } } }
const props = defineProps(['initialCounter'])

// counter only uses props.initialCounter as the initial value;
// it is disconnected from future prop updates.
const counter = ref(props.initialCounter)
  • プロパティを、変換が必要なそのままの値として渡したい。 この場合、以下のような算出プロパティを定義して、その中でプロパティの値を利用するのが最も適切です:export default { props: ['size'], computed: { // プロパティが変更されると自動的に更新される算出プロパティ normalizedSize() { return this.size.trim().toLowerCase() } } }
const props = defineProps(['size'])

// computed property that auto-updates when the prop changes
const normalizedSize = computed(() => props.size.trim().toLowerCase())

オブジェクト/配列のプロップを変更する

オブジェクトや配列をプロパティとして渡した場合、子コンポーネントがプロパティのバインディングを変更することはできませんが、オブジェクトや配列のネストされたプロパティを変更することは可能です。これは、JavaScript ではオブジェクトや配列が参照渡しであり、Vue がそのような変更を防ぐのにかかるコストが現実的でないためです。

このような変更の主な欠点は、親コンポーネントにとって明瞭でない方法で子コンポーネントが親の状態に影響を与えることを許してしまい、後からデータの流れを見極めるのが難しくなる可能性があることです。親と子を密に結合させる設計でない限り、ベストプラクティスとしてはそのような変更を避けるべきです。ほとんどの場合、子コンポーネントはイベントを発行して、変更を親コンポーネントに実行してもらう必要があります。

プロパティのバリデーション

先ほど見た型のように、コンポーネントではプロパティに対する要件を指定することができます。要件が合わないと、Vue がブラウザーの JavaScript コンソールで警告を発します。他の人に使ってもらうことを想定したコンポーネントを開発する場合、これはとても便利です。

プロパティのバリデーションを指定するには、文字列の配列の代わりに props オプションを用いて、バリデーションの要件を持たせたオブジェクトを指定します。例:

defineProps({
  // Basic type check
  //  (`null` and `undefined` values will allow any type)
  propA: Number,
  // Multiple possible types
  propB: [String, Number],
  // Required string
  propC: {
    type: String,
    required: true
  },
  // Number with a default value
  propD: {
    type: Number,
    default: 100
  },
  // Object with a default value
  propE: {
    type: Object,
    // Object or array defaults must be returned from
    // a factory function. The function receives the raw
    // props received by the component as the argument.
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // Custom validator function
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // Function with a default value
  propG: {
    type: Function,
    // Unlike object or array default, this is not a factory function - this is a function to serve as a default value
    default() {
      return 'Default function'
    }
  }
})


//<script setup>を使わないやり方↓
export default {
  props: {
    // 基本的な型チェック
    // (`null` 値と `undefined` 値は、任意の型を許可します)
    propA: Number,
    // 複数の型の可能性
    propB: [String, Number],
    // 必須の文字列
    propC: {
      type: String,
      required: true
    },
    // デフォルト値を持つ数値
    propD: {
      type: Number,
      default: 100
    },
    // デフォルト値を持つオブジェクト
    propE: {
      type: Object,
      // オブジェクトと配列のデフォルトは、ファクトリー関数を使って
      // 返す必要があります。ファクトリー関数は、コンポーネントが
      // 受け取った生の各プロパティを引数として受け取ります。
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // カスタムのバリデーター関数
    propF: {
      validator(value) {
        // 値が以下の文字列のいずれかに一致する必要がある
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // デフォルト値を持つ関数
    propG: {
      type: Function,
      // オブジェクトや配列のデフォルトと異なり、これはファクトリー関数ではなく、デフォルト値として機能する関数です
      default() {
        return 'Default function'
      }
    }
  }
}

その他の詳細:

  • required: true が指定されていないすべてのプロパティは、デフォルトでオプションです。
  • Boolean 以外のオプションのプロパティは、値が指定されないと undefined 値になります。
  • Boolean のプロパティは、値が指定されないと false に変換されます。望みの動作を得るためには、default の値を指定する必要があります。
  • default の値を指定すると、プロパティの値が undefined に解決される時、それが使用されます。プロパティが指定されなかった場合と、明示的に undefined 値が渡された場合も、これに含まれます。

プロパティのバリデーションに失敗すると、Vue がコンソールに警告を出します (開発ビルドを使用する場合)。

注意

プロパティのバリデーションは、コンポーネントのインスタンスが生成されるに実行されます。そのため、default や validator 関数の中ではインスタンスのプロパティ (例えば datacomputed など) が使用できないことに注意してください。

実行時の型チェック

type には、以下のネイティブコンストラクターを指定することができます:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

加えて、type にはカスタムのクラスやコンストラクター関数を指定することもできます。その場合、instanceof チェックによってアサーションが行われます。例えば、次のクラスがあったとします:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

これをプロパティの型として用いるとします:

defineProps({
  author: Person
})

//<script setup>を使わないやり方↓
export default {
  props: {
    author: Person
  }
}

こうすると、author というプロパティの値が new Person で作成されたものであることをバリデーションで確かめることができます。

ブール値の型変換

Boolean 型のプロパティは、ネイティブのブール値の属性が振る舞う様子を模倣するために、特殊な型変換の規則を持っています。次のような宣言を含む <MyComponent> があるとします:

export default {
  props: {
    disabled: Boolean
  }
}

このコンポーネントは、次のように使用することができます:

<!-- :disabled="true" を渡すのと同等 -->
<MyComponent disabled />

<!-- :disabled="false" を渡すのと同等 -->
<MyComponent />

また、以下のように複数の型を受け付けるようにプロパティを宣言した場合:

export default {
  props: {
    disabled: [Boolean, Number]
  }
}

Boolean の型変換の規則は、型が登場する順序に関係なく適用されます。