2016/10/26

Vue.js2.x系で親子コンポーネント間でデータの受け渡しをする方法

引用: Components#Composing Components - vue.js

Vue.jsで親子コンポーネント間でのデータの受け渡しは、原則としてPass Props/Emit Eventsで行う。

親コンポーネントから子コンポーネントへデータを渡すときはPropsを使い、子コンポーネントから親コンポーネントへデータを渡すときはEmitでイベントを発火させる、ということらしい。
ただ、公式ドキュメントに載っている図を見るだけではよくわからなかったので、実際にサンプルを作ってみた。


親子コンポーネント間でデータの受け渡しをする方法


<div id="app">
  <div class="parent">
    <h2>Parent</h2>
    <!-- 
      子コンポーネントからのデータを表示
      親.applyメソッドで更新される
    -->
    <p>{{ messageFromChild }}</p>

    <!-- 親.onInputでリアルタイムに更新される -->
    <p>{{ messageVModel }}</p>
  </div>

  <!--
    parent-messageは静的に値を渡している
    子コンポーネントで$emit(’apply-from-child')で親.applyメソッドが実行される
    v-modelはprops['value']で受け取ることができる
  -->
  <my-child parent-message="literal-from-parent" v-on:apply-from-child="apply" v-model="messageVModel"></my-child>
</div>
Vue.component('my-child', {
  template: [
    '<div class="child">',
    '<h2>Child Component</h2>',
    '<input type="text" v-model="childMessage" v-on:input="onInput"></input>',
    '<button @click="apply">apply</button>',
    '<p>{{ parentMessage }}</p>',
    '<p>{{ value }}</p>',
    '</div>'
  ].join(''),
  props: [
    'parentMessage',  // 親コンポーネントの`parent-message`を`parentMessage`で受け取る
    'value'           // 親コンポーネントの`v-model`を`value`で受け取る
  ],
  data () {
    return {
      childMessage: ''
    }
  },
  methods: {
    apply () {
      // @click: ボタンを押したときに`apply-from-child`イベントを発火させて親コンポーネントにデータを渡す
      this.$emit('apply-from-child', this.childMessage)
    },
    onInput (e) {
      // v-on:input: 値を入力したときにinputイベントを発火させて、親コンポーネントのv-modelを更新する
      this.$emit('input', e.target.value)
    }
  }
})

const app = new Vue({
  el: '#app',
  data () {
    return {
      messageFromChild: 'message from child',
      messageVModel: 'message v-model'
    }
  },
  methods: {
    apply (value) {
      // 子コンポーネントのapplyメソッドからイベントが発火される
      this.messageFromChild = value
    }
  }
})

これを実際に実装すると、以下のようになる。


Props: 親コンポーネントから子コンポーネントにデータを渡す


親子コンポーネントから子コンポーネントにデータを渡すときは、Propsを使う。
前述のコードに説明を書き加えてみた。
今回は2つの方法で、子コンポーネントにデータを渡している。

1つ目は静的なProps。<my-child parent-message="literal-from-parent">の部分だ。
parent-message="literal-from-parent"」で親コンポーネントから`literal-from-parent`という文字列が渡り、子コンポーネントのprops: ['parentMessage']でデータを受けとている。

ちなみにHTML内に書くときはケバブケース(kebab-case)で、JavsScript内に書くときはキャメルケース(camelCase)を使う。

2つ目はv-modelを使った方法。<my-child v-model="messageVModel">の部分だ。
v-modelは<my-child v-bind:value="messageVModel" v-on:input="messageVModel = $event.target.value">のシンタックスシュガーなので、子コンポーネントではprops: ['value']で値を受け取ることができる。


より詳しい内容は以下の記事を参照してほしい。



Emit: 子コンポーネントから親コンポーネントにデータを渡す


子コンポーネントから親コンポーネントにデータを渡すときは、Emitを使う。
同じく、前述のコードに説明を書き加えてみた。
今回は、Propsと同様に2つの方法で、親コンポーネントにデータを渡している。

1つ目は、ボタンを押下したとき。<button @click="apply">apply</button>の部分だ。(図中では青で囲われている部分)
ボタンをクリックすることで、子コンポーネントのmethodsに定義されているapplyメソッドが呼ばれる。そこでthis.$emit('apply-from-child, this.childMessage)でイベントを発火させている。第1引数がイベント名、第2引数が親コンポーネントへ渡すデータ。

親コンポーネントは、<my-child v-on:apply-from-child="apply">でapply-from-childイベントを受け取り、親コンポーネントのmethodsに定義されているapplyメソッドが呼び出され、自身のmessageFromChildが更新される。


2つ目は、テキストボックスに入力したとき。<input type="text" v-model="childMessage" v-on:input="onInput"></input>の部分だ。(図中ではピンクで囲われている部分)
テキストボックスに入力するとv-on:input="onInput"により、子コンポーネントのonInputメソッドが呼ばれる。そこで先ほどと同様に、this.$emit('input', e.target.value)でinputイベントを発火させている。

v-modelは<my-child v-bind:value="messageVModel" v-on:input="messageVModel = $event.target.value">のシンタックスシュガーだということは説明済みだが、inputイベントが発火され、親コンポーネントのv-on:input="messageVModel = $event.target.value"により、messageVModelが更新される。


このようにして子コンポーネントから親コンポーネントにデータを渡している。



親子関係にないコンポーネント間でデータを渡す


これについては今回のサンプルコードにはないが、イベント発火を使って行う。
const vm = new Vue()

// 値を受け取る側
vm.$on('non-parent-child-communication', (value) => {
    // 受け取ったvalueでいろんな処理
})

// 値を渡す側
vm.$emit('non-parent-child-communication', '渡したいデータ')

いわゆる普通のイベントモデルだ。
$onでイベントリスナーを登録して、$emitでイベントを発火させて、$onで通知を受け取る。

今回では「vm」という空のVueインスタンスを使っている。これをグローバル環境において、イベントを監視させたりできる。



アンチパターン: this.$parent


ドキュメントをひと通り読んだけど、やっぱり英語だと正確なことが読み取れない。公式ドキュメントを途中で諦めてググったら「this.$parent.hoge」で親コンポーネントのデータにアクセスできるというサンプルコードをみつけた。
とりあえずそれでできるならと実装していたものの、やっぱり疑問に思いツイートしたところ、Vue.jsおじさんこと@kazu_ponさんにリプライをいただいた。

どうやら、$parent経由でのやりとりはアンチパターンらしい。
このときに教えていただいたURLから公式ドキュメントを読んで、ようやく理解できた。

ご指摘、ありがとうございました。


参考サイト





以上

written by @bc_rikko

0 件のコメント :

コメントを投稿