Vue.js で親コンポーネントと子コンポーネントのデータを同期する手法として、v-model
や sync
を利用する方法がありますが、それぞれのサンプルコードを作成して比較してみました。
まず、Vue.jsで親コンポーネントから子コンポーネントへデータ(初期値)を受け渡す場合は、props
を利用します。
また、反対に子コンポーネントから親コンポーネントへデータを受け渡す場合は、$emit
を利用します。
以下は、v-model
や sync
を利用せずに親子間でデータを受け渡しをおこなう際の例になります。
子コンポーネントから、$emit
を利用して親コンポーネントの @input
をコールしています。
// 親コンポーネント (ChildRows.vue)
<template>
<ul>
<template v-for="(e, i) in people">
<ChildRow
:key="i"
:index="i"
:default-person="e"
@input="handleOnChange"
/>
</template>
</ul>
</template>
// 子コンポーネント (ChildRow.vue)
<template>
<li class="row align-items-start">
<div class="col">
名前:
<input
type="text"
name="name"
:value="person.name"
@input="(e) => handleOnChange({ name: e.target.value })"
/>
</div>
<div class="col">
年齢:
<input
type="text"
name="age"
:value="person.age"
@input="(e) => handleOnChange({ age: e.target.value })"
/>
</div>
{{ $data }}
</li>
</template>
<script>
export default {
props: {
index: {
type: Number,
required: true,
},
defaultPerson: {
type: Object,
default: () => {},
required: true,
},
},
data() {
return {
person: {},
};
},
mounted: function () {
this.person = { ...this.defaultPerson };
},
methods: {
handleOnChange: function (data) {
this.person = { ...this.person, ...data };
},
},
watch: {
person: function (after, before) {
this.$emit("input", this.index, after);
},
},
};
</script>
しかし、この手法の場合、子の変更を親に伝搬することは可能ですが、親の変更を子に再度伝搬することが出来ません。
また、コード量が多く可読性が悪くなってしまいます。
子コンポーネント1のフォームを変更した場合、親コンポーネントには変更が伝搬されても、子コンポーネント2の値は変更されず、親の変更が子に伝搬されない事が確認できるかと思います。
v-modelを利用して同期する方法
v-model
を利用することで、親の変更を子に伝搬することが出来るため、常に同期をとることが出来るようになります。
子コンポーネント1のフォームを変更すると、子コンポーネント2の値も同様に変更されることが確認できるかと思います。
// 親コンポーネント (ChildRows.vue)
<template>
<ul>
<template v-for="(e, i) in defaultPeople">
<ChildRow :key="i" :default-person="defaultPeople[i]" />
</template>
</ul>
</template>
// 子コンポーネント (ChildRow.vue)
<template>
<li class="row align-items-start">
<div class="col">
名前:
<input type="text" name="name" v-model="person.name" />
</div>
<div class="col">
年齢:
<input type="text" name="age" v-model="person.age" />
</div>
</li>
</template>
<script>
export default {
props: {
defaultPerson: {
type: Object,
default: () => {},
required: true,
},
},
computed: {
person: {
get() {
return this.defaultPerson;
},
set(v) {
this.$emit("change", v);
},
},
},
};
</script>
syncを利用して同期する方法
sync
を利用した場合も、v-model
と同様に、親の変更を子に伝搬することが出来るため、常に同期をとることが出来るようになります。
子コンポーネント1のフォームを変更すると、子コンポーネント2の値も同様に変更されることが確認できるかと思います。
v-model
のコードと比べると、computed
の記述が不要となる分、可読性がよくなりそうです。
// 親コンポーネント (ChildRows.vue)
<template>
<ul>
<template v-for="(e, i) in defaultPeople">
<ChildRow :key="i" :default-person.sync="defaultPeople[i]" />
</template>
</ul>
</template>
// 子コンポーネント (ChildRow.vue)
<template>
<li class="row align-items-start">
<div class="col">
名前:
<input
type="text"
name="name"
:value="defaultPerson.name"
@input="(e) => handleOnChange({ name: e.target.value })"
/>
</div>
<div class="col">
年齢:
<input
type="text"
name="age"
:value="defaultPerson.age"
@input="(e) => handleOnChange({ age: e.target.value })"
/>
</div>
{{ $data }}
</li>
</template>
<script>
export default {
props: {
defaultPerson: {
type: Object,
default: () => {},
required: true,
},
},
methods: {
handleOnChange: function (data) {
this.$emit("update:defaultPerson", { ...this.defaultPerson, ...data });
},
},
};
</script>