skydum

個人的な作業記録とか備忘録代わりのメモ

Vue 2でコンポーネント間のデータのやり取りを行う

Vue 2でコンポーネント間のデータのやり取りを行う

  • 普段Vueをそれほど頻繁には使わないのでv-model、.syncを使った方法をよく忘れるので備忘録として記載する
  • 記載してあるソース全文についてはindex.htlmの様にコピペしてローカルでそのまま動きます。(VueCLIは不要)

  • Vue.js でコンポーネント間のデータのやり取りを行うにはいくつかの方法がある。

    1. props を利用する
    2. v-modelを利用する
    3. .syncを利用する
  • コンポーネント間のやり取りを行うために以下のルールがある
    1. 親のコンポーネントから子供のコンポーネントへ渡したデータは子供側で変更を行ってはいけない
    2. 子供から親へデータを渡すには$emitを使って渡す

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>


データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. 子供のコンポーネントの parentData が更新され、parentData が更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit が実行されて親側の parentReciveData メソッドが実行される
  5. 親側の parentReviceData が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220328220046p:plain
propsの場合

ソース

  • 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>


データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. v-model経由で子供のコンポーネントのprops: ["value"]へ 値が渡され、value が更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のinputメソッドが実行される
  5. 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220329222225p:plain
v-model

v-modelとprops: ["value"]と$emit("input")の関係

f:id:Phoca:20220329214142p:plain
v-model糖衣構文

<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>

データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. :parent-data.sync経由で子供のコンポーネントのprops: ["parentData"]へ 値が渡され、parentDataが更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のupdate:parent-dataメソッドが実行される
  5. 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220329222416p:plain
sync

:parent-data.syncとupdate:parent-dataの関係

f:id:Phoca:20220402211050p:plain
sync-syntax-sugar

<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);
            },
        },
    },
});