Vue.js | コンポーネントと親子間のデータ送受信

コンポーネントを使うと、サイトをヘッダー、メニュー、商品一覧、検索フォーム、個別の商品項目などの再利用可能な部品へ分けられる。

ショッピングカート

UIをコンポーネント化すると、JavaScriptとHTML/CSSを分けて管理でき、保守性が上がり、必要な画面で同じ部品を再利用できる。

コンポーネントを作成する

コンポーネントでは、ルートのnew Vue()に渡すオプションと同じように、データ、メソッド、テンプレートを定義する。

Vue.component("my-component", {
  template: "<p>example</p>"
});

Vue.componentで登録するとグローバルコンポーネントになり、どこからでも利用できる。特定のコンポーネントやルートインスタンスだけで使う場合は、componentsオプションへ登録する。

var myComponent = { template: "<p>example</p>" };
new Vue({
  components: {
    "my-component": myComponent
  }
});

Vue.extendでサブクラスを作り、それをコンポーネントとして登録することもできる。

var myComponent = Vue.extend({ template: "<p>example</p>" });

コンポーネントを呼び出す

コンポーネントを表示したい場所にカスタムタグを書く。

<div id="app">
  <my-component></my-component>
</div>

classなどの属性は、コンポーネントテンプレートのルート要素と結合される。条件によってコンポーネントを切り替えたい場合はisを使う。

<div is="my-component"></div>
<div :is="currentComponent"></div>

dataは関数にする

コンポーネントのdataは、オブジェクトを返す関数として書く。これにより、コンポーネントインスタンスごとにデータが分離される。

Vue.component("my-component", {
  template: "<p>{{ message }}</p>",
  data: function() {
    return { message: "hello!" };
  }
});

親から子へデータを渡す

親のデータを子コンポーネントに表示するには、属性として渡す。通常の属性は文字列を渡し、v-bindまたは:を使うとデータ値や式を渡せる。

<child-component val="message"></child-component>
<child-component :val="message"></child-component>

子はpropsで値を受け取る。

Vue.component("child-component", {
  template: "<p>{{ val }}</p>",
  props: {
    val: String
  }
});

propsは親から借りている値なので、子が直接書き換えるべきではない。変更を親へ伝えたい場合はイベントを発行する。

子から親へデータを送る

子が親へ通知したり値を渡したりする場合は、カスタムイベントと$emitを使う。

<child-component @childs-event="parentsMethod"></child-component>
this.$emit("childs-event", "hello!");

親のメソッドは、子から送られた値を受け取れる。

new Vue({
  methods: {
    parentsMethod: function(message) {
      alert(message);
    }
  }
});

インライン式で受ける場合、子が渡した値は$eventとして利用できる。

親子間通信の例

実用的な形として、親のデータはpropsで子へ渡し、子のデータはカスタムイベントで親へ返す。

<child-component
  :parent-message="parentMessage"
  @send-message="getChildMessage"></child-component>
var childComp = Vue.extend({
  props: { parentMessage: String },
  data: function() {
    return { childMessage: "This is child data" };
  },
  created: function() {
    this.$emit("send-message", this.childMessage);
  }
});

明確な理由がない限り、$parent$childrenへ強く依存するのは避ける。propsとイベントを使うと、コンポーネントの結合を弱く保てる。

インタラクティブなコンポーネント

より実用的な例として、友人一覧をリストコンポーネントとプロフィールコンポーネントに分けられる。「いいね」をクリックするとプロフィールコンポーネントからイベントが発行され、親がカウントを増やし、算出プロパティでカウント順に並べ替える。

<friends-profile
  v-for="(friend, idx) in sortedFriends"
  v-bind="friend"
  :key="friend.id"
  @count-trigger="countUp(idx)">
</friends-profile>
var friendsList = Vue.extend({
  data: function() {
    return {
      friends: [
        { id: 1, name: "Cat", color: "#e69313", count: 0 },
        { id: 2, name: "Fox", color: "#a7a264", count: 0 },
        { id: 3, name: "Raccoon", color: "#bbbbbb", count: 0 }
      ]
    };
  },
  computed: {
    sortedFriends: function() {
      return this.friends.sort(function(a, b) {
        return b.count - a.count;
      });
    }
  },
  methods: {
    countUp: function(idx) {
      this.friends[idx].count++;
    }
  }
});

カスタムコンポーネントに書いたイベントはカスタムイベントとして扱われる。通常のDOMイベントを受けたい場合は.native修飾子を使う。

<my-comp @click.native="handleClick"></my-comp>

コンポーネントでv-modelを使う

コンポーネントはv-modelを使うことでフォーム部品のように扱える。コンポーネントがinputイベントを発行すると、親の値が更新される。

<my-select v-model.number="current"></my-select>
this.$emit("input", newid);

少し凝ったカスタムフォーム部品を作るときに便利である。

Vue.extendについて

Vue.extendはコンストラクタを返す。newでインスタンスを作成し、独立した要素へマウントできる。単体テストや、モーダルウィンドウのようなライブラリ機能を作る場合によく使える。

拡張コンポーネントを独立してマウントすると、それ自体がルートになる。既存インスタンスの子として扱いたい場合は、parentオプションを渡すか、同じstoreを渡す。

this.sub1 = new ChildComp({ el: "#sub1", parent: this });
this.sub2 = new ChildComp({ el: "#sub2", store: store });