Vue.jsで親子のデータを同期するならsyncを使え

Vue.js で親コンポーネントと子コンポーネントのデータを同期する手法として、v-model sync を利用する方法がありますが、それぞれのサンプルコードを作成して比較してみました。

まず、Vue.jsで親コンポーネントから子コンポーネントへデータ(初期値)を受け渡す場合は、propsを利用します。

また、反対に子コンポーネントから親コンポーネントへデータを受け渡す場合は、$emitを利用します。

以下は、v-modelsync を利用せずに親子間でデータを受け渡しをおこなう際の例になります。

子コンポーネントから、$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>

コメントを残す

入力エリアすべてが必須項目です。メールアドレスが公開されることはありません。

内容をご確認の上、送信してください。