VueJS 速習チュートリアル

Vue Props

1. コンポーネントへのデータ渡過

Props(プロップス) は、Vue の設定オプションの一つです。Props を使用すると、コンポーネントタグにカスタム属性を追加することで、コンポーネントへデータを渡すことができます。

前ページでは、3つのコンポーネントすべてに「Apples(リンゴ)」と表示される例を確認しました。Props を使用すれば、各コンポーネントに異なるデータを流し込み、それぞれ異なる内容を表示させることが可能になります。

まずは、「リンゴ」「ピザ」「ライス」を表示するシンプルなページを作成してみましょう。

メインのアプリケーションファイルである App.vue で、<food-item/> コンポーネントタグに独自の属性 food-name を作成し、データを渡します。

App.vue:

<template>
  <h1>食べ物</h1>
  <food-item food-name="リンゴ"/>
  <food-item food-name="ピザ"/>
  <food-item food-name="ライス"/>
</template>

<script></script>

<style>
  #app > div {
    border: dashed black 1px;
    display: inline-block;
    width: 120px;
    margin: 10px;
    padding: 10px;
    background-color: lightgreen;
  }
</style>

2. コンポーネント内部でのデータ受信

App.vue から food-name 属性を通じて送られたデータを受信するには、props 設定オプションを使用します。受信する属性をリストアップすることで、コンポーネントファイルがそれらを認識し、データプロパティ と同じようにどこでも使用できるようになります。

FoodItem.vue:

<script>
  export default {
    props: [
      'foodName'
    ]
  }
</script>

ここで重要なルールがあります。<template> タグ内では、属性名は単語をダッシュで区切る ケバブケース(kebab-case) で記述しますが、JavaScript ではハイフンは使用できません。そのため、JavaScript 側では キャメルケース(camelCase) で記述する必要があります。Vue はこれを自動的に関連付けて理解してくれます。

最終的に、「リンゴ」「ピザ」「ライス」を表示する <div> 要素の例は以下のようになります。

2.1 実装例:基本的な Props の受け渡し

App.vue:

<template>
  <h1>食べ物</h1>
  <food-item food-name="リンゴ"/>
  <food-item food-name="ピザ"/>
  <food-item food-name="ライス"/>
</template>

FoodItem.vue:

<template>
  <div>
    <h2>{{ foodName }}</h2>
  </div>
</template>

<script>
  export default {
    props: [
      'foodName'
    ]
  }
</script>

<style></style>

3. さまざまデータ型の受け渡しとレイアウト拡張

次に、各食べ物の「説明文」を追加し、これらの要素を Flexbox ラッパーで囲むようにコードを拡張してみましょう。

3.1 実装例:複数 Props とレイアウト

App.vue:

<template>
  <h1>食べ物</h1>
  <div id="wrapper">
    <food-item
      food-name="リンゴ"
      food-desc="リンゴは木に実る果物の一種です。"/>
    <food-item
      food-name="ピザ"
      food-desc="ピザはパン生地をベースに、トマトソース、チーズ、トッピングを乗せたものです。"/>
    <food-item
      food-name="ライス"
      food-desc="ライスは多くの人々に愛されている穀物の一種です。"/>
  </div>
</template>

<script></script>

<style>
  #wrapper {
    display: flex;
    flex-wrap: wrap;
  }
  #wrapper > div {
    border: dashed black 1px;
    margin: 10px;
    padding: 10px;
    background-color: lightgreen;
  }
</style>

FoodItem.vue:

<template>
  <div>
    <h2>{{ foodName }}</h2>
    <p>{{ foodDesc }}</p>
  </div>
</template>

<script>
  export default {
    props: [
      'foodName',
      'foodDesc'
    ]
  }
</script>

<style></style>

4. ブーリアン型の Props

Props にはさまざまなデータ型を渡すことができ、コンポーネント作成時のルールを定義できます。

新しく isFavorite という Props を追加してみましょう。これは true または false を持つ ブーリアン(Boolean) 型とし、お気に入りの場合にのみ v-show を使ってお気に入りマークの <img> を表示するために使用します。

文字列以外のデータ型 を Props として渡すには、属性名の前に v-bind:(または省略形の :)を記述する必要があります。

App.vue:

<template>
  <h1>食べ物</h1>
  <p>お気に入りの食べ物には「修了証」の画像が表示されます。</p>
  <div id="wrapper">
    <food-item
      food-name="リンゴ"
      food-desc="リンゴは木に実る果物の一種です。"
      v-bind:is-favorite="true"/>
    <food-item
      food-name="ピザ"
      food-desc="ピザはパン生地をベースに、トマトソース、チーズ、トッピングを乗せたものです。"
      v-bind:is-favorite="false"/>
    <food-item
      food-name="ライス"
      food-desc="ライスは多くの人々に愛されている穀物の一種です。"
      v-bind:is-favorite="false"/>
  </div>
</template>

4.1 実装例:ブーリアン値による条件付き表示

FoodItem.vue:

<template>
  <div>
    <h2>
      {{ foodName }}
      <img src="/img_quality.svg" v-show="isFavorite">
    </h2>
    <p>{{ foodDesc }}</p>
  </div>
</template>

<script>
  export default {
      props: ['foodName', 'foodDesc', 'isFavorite']
  }
</script>

<style>
  img {
    height: 1.5em;
    float: right;
  }
</style>

5. Props インターフェース

これまでの例では、isFavorite が確実に受け取れているか、それが本当にブーリアン型であるかを確証する方法がありませんでした。これを解決するために、受信する Props の データ型(Type) を定義し、必須(Required) 設定や、独自な バリデーション関数 を設定することができます。

Props の定義を詳細に記述することは、チーム開発におけるドキュメント代わりになるだけでなく、定義したルールが破られた際にブラウザの コンソール(Console) に警告を表示してくれるというメリットがあります。

6. オブジェクト形式での Props 定義

FoodItem.vue において、配列形式での定義ではなく、オブジェクト形式で定義します。これにより、各 Props の名前に加えてデータ型も指定できます。

FoodItem.vue:

<script>
  export default {
    // props: ['foodName','foodDesc','isFavorite'] ← 配列形式はコメントアウト
    props: {
      foodName: String,
      foodDesc: String,
      isFavorite: Boolean
    }
  }
</script>

このように定義すれば、他の開発者が FoodItem.vue を見たときに、コンポーネントが何を期待しているか一目でわかります。もし親要素(App.vue)から間違ったデータ型の Props が渡されると、コンソールに警告が表示されます。

7. 必須項目(Required)の設定

特定の Props が必須であることを Vue に伝えるには、プロパティをさらにオブジェクトとして定義します。foodName を必須にする例です:

FoodItem.vue:

<script>
  export default {
    props: {
      foodName: {
        type: String,
        required: true
      },
      foodDesc: String,
      isFavorite: Boolean
    }
  }
</script>

もし必須の Props が定義されずにコンポーネントが作成されると、開発者はコンソール警告を通じて間違いを即座に修正できます。

8. デフォルト値(Default Value)の設定

Props に デフォルト値(既定値) を設定することも可能です。
foodDesc にデフォルト値を設定し、App.vue 側で説明文を定義せずにライスを表示してみましょう。

8.1 実装例:デフォルト値の活用

FoodItem.vue:

<script>
  export default {
    props: {
      foodName: {
        type: String,
        required: true
      },
      foodDesc: {
        type: String,
        required: false,
        default: 'これはデフォルトの説明文です。'
      },
      isFavorite: {
        type: Boolean,
        required: false,
        default: false
      }
    }
  }
</script>

9. Props バリデータ関数

独自の バリデータ関数(Validator Function) を定義して、Props の値が有効かどうかを判定することもできます。

この関数は true または false を返す必要があります。false が返された場合、Props は無効とみなされ、開発モードのコンソールに警告が表示されます。

例えば、説明文の長さを 20 文字から 50 文字の間に制限したい場合、以下のように記述します。

FoodItem.vue:

<script>
  export default {
    props: {
      foodName: {
        type: String,
        required: true
      },
      foodDesc: {
        type: String,
        required: false,
        default: 'これはデフォルトの説明文です。',
        validator: function(value) {
          // 長さが20文字より大きく、50文字未満であるかチェック
          if( 20 < value.length && value.length < 50 ) {
            return true;
          }
          else {
            return false;
          }
        }
      },
      isFavorite: Boolean
    }
  }
</script>

10. Props の値を変更する(単一方向データフロー)

子コンポーネント内で、親から受け取った Props の値を直接変更することは 禁止 されています。Props は親コンポーネント(ここでは App.vue)からの一方通行のデータであり、子側では 読み取り専用(Read-only) です。

もし、以下のように Props を直接変更しようとするとエラーが発生します:

methods: {
  toggleFavorite() { 
    // これはエラーになります!
    this.isFavorite = !this.isFavorite; 
  }
}

この制限を回避するには、受け取った Props を初期値として新しい データプロパティ(例:foodIsFavorite)を FoodItem.vue 内に作成します。

data の定義:

data() {
  return { 
    foodIsFavorite: this.isFavorite
  }
}

これで、ユーザーがボタンをクリックしてこの新しいローカルデータを切り替えることが可能になります。

10.1 実装例:読み取り専用 Props をローカルデータへ変換

FoodItem.vue:

<template>
  <div>
    <h2>
      {{ foodName }}
      <img src="/img_quality.svg" v-show="foodIsFavorite">
    </h2>
    <p>{{ foodDesc }}</p>
    <button v-on:click="toggleFavorite">お気に入り</button>
  </div>
</template>

<script>
export default {
  props: ['foodName', 'foodDesc', 'isFavorite'],
  data() {
    return {
      // Props を初期値として受け取る
      foodIsFavorite: this.isFavorite
    }
  },
  methods: {
    toggleFavorite() {
      // ローカルデータなら変更可能
      this.foodIsFavorite = !this.foodIsFavorite;
    }
  }
};
</script>

<style>
  img {
    height: 1.5em;
    float: right;
  }
</style>