基礎からメモ: Vue.js CH5-p153 コンポーネント間の通信
コンポーネントのスコープ
コンポーネントのインスタンスはそれぞれが自分のスコープを持っている。
スコープ内のデータやメソッドには、this
を使ってアクセスできる。
this.message // スコープ内データ this.update // スコープ内メソッド
コンポーネントは、自分以外のコンポーネントのデータやメソッドに直接アクセスすることはできない。そこでコンポーネント間でデータ共有や連携するための仕組みが必要になる。
親子コンポーネント
あるコンポーネントのテンプレート内で他のコンポーネントのカスタムタグを使うと、親子関係になる。
<div id="app"> <my-component></my-component> </div>
/* 親子関係のコンポーネント */ Vue.component('comp-child', { template: '<p>My Child Component</p>' }); let myComponent = { template: '<div><comp-child></comp-child></div>', }; let app = new Vue({ el: '#app', data: { //........ }, methods: { //........ }, components: { 'my-component': myComponent } });
表示結果
<div id="app"> <div> <p>My Child Component</p> </div> </div>
ルートインスタンスも中に子コンポーネントを含むとみなすと、便宜上親コンポーネントとして解釈される。子コンポーネントの中に、別の子コンポーネントを含むこともでき、階層化されたそれらのコンポーネントは、DOMツリーのように構造化される。
props
を用いた親から子へのデータ渡し
例えば親コンポーネントで子コンポーネントを使用する際、属性で子に渡すデータを指定する。ここでは子コンポーネントを2回利用し、それぞれ別の文字列をval
といった属性名で指定する。
// 親コンポーネント let myComponent = { template: `<div> <comp-child val="これは子A"></comp-child> <comp-child val="これは子B"></comp-child> </div>`, }; /** #app 側で親コンポーネントを利用 */ let app = new Vue({ el: '#app', //.................. components: { 'my-component': myComponent } });
子コンポーネント側でprops
オプションで親から受け取りたい属性名を指定しておく。
// 子コンポーネント Vue.component('comp-child', { template: `<p>{{ val }} in Child Component</p>`, props: ["val"] });
表示結果
<div id="app"> <div> <p>これは子A in Child Component</p> <p>これは子B in Child Component</p> </div> </div>
このやり方でインスタンスごとに別々のデータを持たせることができる。
データバインディングで親コンポーネントのリアクティブなデータを子に渡す
<comp-child v-bind:val="message" class="item"></comp-child>
ローカルに定義されたコンポーネント間で親から子へデータを渡す。
データバインディングを使えば、ローカルに定義されたコンポーネント間で親から子へデータを渡すことも問題なくできる。
<div id="app"> <my-component></my-component> </div>
メインコンストラクタの外部で、親コンポーネントをローカルに定義しておく。
let msg_data = { message: 'Message from Parent Comp!' }; let myComponent = { template: `<div> <comp-child v-bind:val="p_message" class='parent'></comp-child> </div>`, data: function() { return { p_message: msg_data.message } } }; let app = new Vue({ el: '#app', components: { 'my-component': myComponent } });
コード上は親コンポーネントの前に、子コンポーネントもローカルに定義しておく。ここは共通利用する子コンポーネントならグローバルな定義でもよいが。
let child_comp = Vue.component('comp-child', { template: '<p id="child" class="child">{{val}}</p>', props: ['val'] });
表示結果
<p id="child" class="child parent">Message from Parent Comp!</p>
コンポーネントに対する属性付与
親コンポーネントの中で利用する子コンポーネントのカスタムタグに属性を与えた場合、props
として受け取っていないものは、子コンポーネント側のテンプレートのルートタグの属性として、上書きまたは、複数値が設定可能なclass
属性などの場合マージされる。
<div id="app"> <my-component></my-component> </div>
// 子コンポーネント Vue.component('comp-child', { template: '<p id="child" class="child">My Child Component</p>' }); // 親コンポーネントのカスタムタグに付与された属性は、子コンポーネントへ上書きまたはマージされる let myComponent = { template: '<div><comp-child id="parent" class="parent"></comp-child></div>', }; let app = new Vue({ el: '#app', data: { //...... }, components: { 'my-component': myComponent } });
表示結果
<div> <p id="parent" class="child parent">My Child Component</p> </div>
子コンポーネントを親コンポーネントでリストレンダリングして複数回使う
<div id="app"> <ul> <comp-child v-for="item in list" v-bind:key="item.id" v-bind:name="item.name" v-bind:hp="item.hp"></comp-child> </ul> </div>
Vue.component('comp-child', { template: '<li>{{ name }} HP.{{ hp }}</li>', props: ['name', 'hp'] }); var app = new Vue({ el: '#app', data: { list: [ { id: 1, name: 'slime', hp: 100 }, { id: 2, name: 'goblin', hp: 200 }, { id: 3, name: 'dragon', hp: 500 }, ] } });
表示結果
<div id="app"> <ul> <li>slime HP.100</li> <li>goblin HP.200</li> <li>dragon HP.500</li> </ul> </div>
props
で受け取ったデータは子の側で勝手に書き換えられない
props
はリアクティブな状態で親側のデータを受け取ることができる。したがって親側でデータが更新されると、子側も同期する。子側のメソッド内でthis
を使って親から渡されたデータにアクセスできる。
ただし、子側で親から渡されたデータを勝手に書き換えることはできない。
<div id="app"> <ul> <comp-child v-for="item in list" v-bind:key="item.id" v-bind:name="item.name" v-bind:hp="item.hp"></comp-child> </ul> </div>
/* props で受け取ったデータを子コンポーネント側で勝手に書き換えることはできない */ Vue.component('comp-child', { template: `<li>{{ name }} HP.{{ hp }} <button v-on:click="doAttack">攻撃する</button></li>`, props: ['name', 'hp'], method: { doAttack: function() { // 勝手に攻撃! this.hp -= 10; } } }); var app = new Vue({ el: '#app', data: { list: [ { id: 1, name: 'slime', hp: 100 }, { id: 2, name: 'goblin', hp: 200 }, { id: 3, name: 'dragon', hp: 500 }, ] } });
子側のテンプレート内にボタンを設置し、クリックすると親のデータの持つhp
を−10するメソッドdoAttack
が起動するようにしてあるが、実際にはエラーになる。
vue.js:597 [Vue warn]: Invalid handler for event "click": got undefined found in ---> <CompChild> <Root> ....................................
これを回避して子コンポーネントでデータを変更したい場合は、算出プロパティで新しいデータを作成するか、元データに変更を加える場合は子から親へ通信を行う$emit
を使う。