Vue 2でコンポーネント間のデータのやり取りを行う
- 普段Vueをそれほど頻繁には使わないのでv-model、.syncを使った方法をよく忘れるので備忘録として記載する
記載してあるソース全文についてはindex.htlmの様にコピペしてローカルでそのまま動きます。(VueCLIは不要)
Vue.js でコンポーネント間のデータのやり取りを行うにはいくつかの方法がある。
- props を利用する
- v-modelを利用する
- .syncを利用する
- コンポーネント間のやり取りを行うために以下のルールがある
props を使ったコンポーネント間のデータのやり取り
- 渡す値が少数ならこの方法でも特に問題はない
ソース全文
props パターンのソース
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> <title>Vue.js props</title> <style> .red { border: solid 2px red; padding: 5px; } .blue { border: solid 2px blue; padding: 5px; } .purple { border: solid 2px black; padding: 5px; } </style> </head> <body> <div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント <input v-model="parentInputData"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- prop-childコンポーネントにparentDataの変数名でparentInputDataの値を渡す --> <!-- porp-childコンポーネントのemitで指定するメソッド名をreveive-dataとして登録 --> <!-- 親側で実際に実行されるメソッド名をparentReceiveDataとして登録 --> <props-child :parent-data="parentInputData" @receive-data="parentReceiveData" class="purple"> </props-child> </div> </div> </div> </div> <script> Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ parentData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, props: ["parentData"], data() { return { childData2: this.parentData, }; }, computed: { childData: { get() { return this.parentData; }, set(data) { // "receive-data"が親側のコンポーネントで指定した@receive-data // data親側のreceiveData(value)の所に入る this.$emit("receive-data", data); }, }, }, }); new Vue({ el: "#app", data: { parentInputData: "あ" }, methods: { parentReceiveData(value) { // 子供側のコンポーネントの$emitで呼び出されるメソッド this.parentInputData = value; } }, }) </script> </body> </html>
データの流れ
- テキストボックスに文字を入れると parentInputData が更新される
- 子供のコンポーネントの parentData が更新され、parentData が更新されると子供側の computed の childData が更新される
- 子供側のテキストボックスの内容が親側と同じ内容で表示される
- 子供側のテキストボックスに文字を入れると computed 経由で$emit が実行されて親側の parentReciveData メソッドが実行される
- 親側の parentReviceData が実行されると親側の parentData が子供側で更新された内容に更新される
ソース
- html
<div id="app"> <div class="red"> <div>テスト</div> <div class="blue"> 親コンポーネント <input v-model="parentInputData" /> </div> <div class="red" style="margin-top: 5px;"> <div> コンポーネント <props-child :parent-data="parentInputData" @receive-data="parentReceiveData" class="purple" > </props-child> </div> </div> </div> </div>
- 親側のインスタンス
// 親側のコンポーネント(vueのインスタンス) new Vue({ el: "#app", data: { parentInputData: "あ", }, methods: { parentReceiveData(value) { // 子供側のコンポーネントの$emitで呼び出されるメソッド this.parentInputData = value; }, }, });
- 子供のコンポーネント
// 子供のコンポーネント Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ parentData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, props: ["parentData"], data() { return { childData2: this.parentData, }; }, computed: { childData: { get() { return this.parentData; }, set(data) { // "receive-data"が親側のコンポーネントで指定した@receive-data // data親側のreceiveData(value)の所に入る this.$emit("receive-data", data); }, }, }, });
v-modelを使ったコンポーネント間のデータのやり取り
- propよりも若干書きやすい
- 親側はスッキリかけるけれども子供側のcomputedにはget, setをたくさん書く必要がある
- v-modelは使った親 → 子供への値の受け渡しにつかえるのは1個だけ(複数個のv-modelを指定することはできない)
ソース全文
v-model パターンのソース
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> <title>Vue.js v-model</title> <style> .red { border: solid 2px red; padding: 5px; } .blue { border: solid 2px blue; padding: 5px; } .purple { border: solid 2px black; padding: 5px; } </style> </head> <body> <div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント <input v-model="parentInputData"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す --> <!-- prop-childコンポーネント側ではv-modelで渡された値はvalueとして受け取る(vue.jsの仕様) --> <props-child v-model="parentInputData" class="purple"></props-child> <!-- 上記は以下のように書いたのと同等となる --> <!-- <props-child :value="parentInputData" @input="parentInputData = $event" /> --> <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html --> </props-child> </div> </div> </div> </div> <script> Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ childData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, // 親側のv-modelで渡された値は子供側ではvalueとして受け取る props: ["value"], data() { return { childData2: this.value, }; }, computed: { childData: { get() { return this.value; }, set(data) { // childDataの値が更新されたら親のコンポーネントのinputを$emitで呼び出す // v-modelで親から子供に値が渡るときはvalueで渡され // 親側に返すときはvue.jsの仕様でinputイベントで返すと決まっている this.$emit("input", data); }, }, }, }); new Vue({ el: "#app", data: { parentInputData: "あ" }, methods: {} }) </script> </body> </html>
データの流れ
- テキストボックスに文字を入れると parentInputData が更新される
- v-model経由で子供のコンポーネントのprops: ["value"]へ 値が渡され、value が更新されると子供側の computed の childData が更新される
- 子供側のテキストボックスの内容が親側と同じ内容で表示される
- 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のinputメソッドが実行される
- 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される
v-modelとprops: ["value"]と$emit("input")の関係
コンポーネントでv-modelを利用した場合はv-model="parentInputData"は子供側ではprops:["value"]となる。
- 子供側から親側へデータを渡すには親側のinputを呼ぶ
<props-child v-model="parentInputData" /> 上記は下記の糖衣構文 <props-child :value="parentInputData" @input="parentInputData = $event" />
- 以下のようにv-modelを複数個記載することはできない
<props-child v-model="parentInputData" v-model="parentInputData2" />
ソース
- html
<div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント <input v-model="parentInputData"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す --> <!-- prop-childコンポーネント側ではv-modelで渡された値はvalueとして受け取る(vue.jsの仕様) --> <props-child v-model="parentInputData" class="purple"></props-child> <!-- 上記は以下のように書いたのと同等となる --> <!-- <props-child :value="parentInputData" @input="parentInputData = $event" /> --> <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html --> </props-child> </div> </div> </div> </div>
- 親側のインスタンス
new Vue({ el: "#app", data: { parentInputData: "あ" }, methods: {} })
- 子供のコンポーネント
Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ childData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, // 親側のv-modelで渡された値は子供側ではvalueとして受け取る props: ["value"], data() { return { childData2: this.value, }; }, computed: { childData: { get() { return this.value; }, set(data) { // childDataの値が更新されたら親のコンポーネントのinputを$emitで呼び出す // v-modelで親から子供に値が渡るときはvalueで渡され // 親側に返すときはvue.jsの仕様でinputイベントで返すと決まっている this.$emit("input", data); }, }, }, });
.syncを使ったコンポーネント間のデータのやり取り
- v-modelと同じ様な感じでデータのやり取りが可能
- v-modelはコンポーネントに対して1個しか利用できなかったが、こちらには制限がない
- v-modelのパターンと同じく子供側のcomputedにはget, setをたくさん書く必要がある
ソース全文
.sync propsが1個のパターンのソース
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> <title>Vue.js .sync</title> <style> .red { border: solid 2px red; padding: 5px; } .blue { border: solid 2px blue; padding: 5px; } .purple { border: solid 2px black; padding: 5px; } </style> </head> <body> <div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント <input v-model="parentInputData"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す --> <props-child :parent-data.sync="parentInputData" /> <!-- 上記は以下のように書いたのと同等となる --> <!-- <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" /> --> <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html --> </div> </div> </div> </div> <script> Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ childData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る props: ["parentData"], data() { return { childData2: this.value, }; }, computed: { childData: { get() { return this.parentData; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data", data); }, }, }, }); new Vue({ el: "#app", data: { parentInputData: "あ" }, methods: {} }) </script> </body> </html>
.sync propsが2個のパターンのソース
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script> <title>Vue.js .sync</title> <style> .red { border: solid 2px red; padding: 5px; } .blue { border: solid 2px blue; padding: 5px; } .purple { border: solid 2px black; padding: 5px; } </style> </head> <body> <div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント1 <input v-model="parentInputData"> </div> <div class="blue">親コンポーネント2 <input v-model="parentInputData2"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す --> <props-child :parent-data.sync="parentInputData" :parent-data2.sync="parentInputData2" /> <!-- 上記は以下のように書いたのと同等となる --> <!-- <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" :parent-data2.sync="parentInputData2" @update:parent-data2="parentInputData2 = $event" /> --> </div> </div> </div> </div> <script> Vue.component("props-child", { template: ` <div> <div>子供コンポーネント1 初期値1:{{ childDataA }} / 更新1: {{ childData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> <div>子供コンポーネント2 初期値2:{{ childDataB }} / 更新2: {{ childData2 }} <div> 親コンポーネントと同期: <input v-model="childData2"></input> </div> </div> </div> `, // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る props: ["parentData", "parentData2"], data() { return { childDataA: this.parentData, childDataB: this.parentData2, }; }, computed: { childData: { get() { return this.parentData; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data", data); }, }, childData2: { get() { return this.parentData2; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentData2を$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data2", data); }, }, }, }); new Vue({ el: "#app", data: { parentInputData: "あ", parentInputData2: "い" }, methods: {} }) </script> </body> </html>
データの流れ
- テキストボックスに文字を入れると parentInputData が更新される
- :parent-data.sync経由で子供のコンポーネントのprops: ["parentData"]へ 値が渡され、parentDataが更新されると子供側の computed の childData が更新される
- 子供側のテキストボックスの内容が親側と同じ内容で表示される
- 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のupdate:parent-dataメソッドが実行される
- 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される
:parent-data.syncとupdate:parent-dataの関係
<props-child :parent-data.sync="parentInputData" /> 上記は下記の糖衣構文 <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" />
- 複数個書くことができる(2個書くときは以下のようにする)
親側 <props-child :parent-data.sync="parentInputData" :parent-data2.sync="parentInputData2" />
子供側computedの所だけ抜粋 computed: { childData: { get() { return this.parentData; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data", data); }, }, childData2: { get() { return this.parentData2; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data2", data); }, }, },
.syncが1個のパターンのソース
- html
<div id="app"> <div class="red"> <div>テスト</div> <div class="blue">親コンポーネント <input v-model="parentInputData"> </div> <div class="red" style="margin-top: 5px;"> <div>コンポーネント <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す --> <props-child :parent-data.sync="parentInputData" /> <!-- 上記は以下のように書いたのと同等となる --> <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" /> <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html --> </div> </div> </div> </div>
- 親側のインスタンス
new Vue({ el: "#app", data: { parentInputData: "あ" }, methods: {} })
- 子供のコンポーネント
Vue.component("props-child", { template: ` <div> <div>子供コンポーネント 初期値:{{ childData2 }} / 更新: {{ childData }} <div> 親コンポーネントと同期: <input v-model="childData"></input> </div> </div> </div> `, // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る props: ["parentData"], data() { return { childData2: this.value, }; }, computed: { childData: { get() { return this.parentData; }, set(data) { // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている this.$emit("update:parent-data", data); }, }, }, });