【Vue】知っておきたい .sync修飾子のすゝめ


Vue.js 2.3.0から実装された 「[.sync 修飾子](https://jp.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A3%BE%E5%AD%90)」がVue.jsを書いていると陥るよくある困りごとを解決したので紹介します！

サンプルコードはこちら↓
https://github.com/harhogefoo/vue-sync-modifier-sample


# 対象読者
- Vue.js 経験者
- 子コンポーネントに複数のプロパティをバインドさせたい人（Object型も含む）
- これから大量の入力フォームを子コンポーネントに分割して実装していくんだぜ...な人

# よくある困りごと
例えば、新規作成(new)、 編集(edit)それぞれのページの共通のフォームのコンポーネントを実装する場合、
そのコンポーネントにフォームの情報を持つObject型のプロパティを渡したくなりますよね。

Vueでは、プロパティを渡した先でプロパティの情報を更新しようとすると、
以下のようなwarningログが出力されます（ただしObject型やArray型を渡した場合は出力されません）。

> [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "propsValue"

```
親コンポーネントが再レンダリングされるたびに値が上書きされるため、propを直接変更しないでください。
代わりに、dataまたはcomputedプロパティを使用して値を変更して下さい。変化するprop： "propsValue"
```

### Object型のプロパティを子コンポーネントに渡し、子コンポーネントで値を変更するリスク
Object型のプロパティを子コンポーネントに渡し、子コンポーネントで値を変更した場合、warningログは出力されません。
そのため、渡した値がどのコンポーネントで変更されているか把握し辛くなってしまいます。

したがって、
**「親の知らぬところで子コンポーネントが値を変更する方法」**はなるべく避け、
**「子が親に通知して親が値を変更する方法」** を採用すると良いでしょう。

今回は「子が親に通知して親が値を変更する」を実現する手段として、`.sync` 修飾子を利用します。
また、`.sync` 修飾子はObject型などを直接バインドすることもできます。

この記事では

- Object型の値ををコンポーネントに渡し、その値をコンポーネント内で更新しwarningを回避する方法
- `.sync`修飾子を利用する方法


を比較しながら、`.sync` 修飾子の使い所を説明していきたいと思います。

# 🤔 Object型のプロパティを渡してwarningを回避する方法
**親の知らぬところで子が値を更新してしまうパターン** を以下に記載します。
確かにこの方法で複数のプロパティを更新するコンポーネントになりますが、
Vueのデータフローの思想に反してしまいます。

```views/Parent.vue
<template>
  <div>
    <user-input-form :user="user" @submit="createUser" />
  </div>
</template>

<script>
import { defaultUser } from "@/helper/user";
import UserInputForm from "@/components/UserInputForm.vue";
export default {
  components: {
    UserInputForm
  },
  data() {
    return {
      user: defaultUser() // { email: '', name: '', ... }
    };
  },
  methods: {
    createUser() {
      console.log(this.user);
    }
  }
};
</script>
```

```UserInputForm.vue
<template>
  <form @submit.prevent="$emit('send')">
    <label>
      メールアドレス
      <input type="text" v-model="user.email" />
    </label>
    <label>
      名前
      <input type="text" v-model="user.name" />
    </label>
    <button type="submit" @click="$emit('submit')">登録</button>
  </form>
</template>

<script>
export default {
  props: {
    user: {
      type: Object,
      default: () => ({})
    }
  }
};
</script>
```

# 👍 `.sync`修飾子を使って値の変更を通知する方法
では、子は親から渡ってきたデータが更新されたことを**子から親に通知する**ために、`.sync`修飾子を使って実現していきましょう。

`:email.sync="user.email"` が今回のポイントです。

```views/Parent.vue
<template>
  <div>
    <user-input-form
      :email.sync="user.email"
      :name.sync="user.name"
      @submit="createUser"
    />
  </div>
</template>

<script>
import { defaultUser } from "@/helper/user";
import UserInputForm from "@/components/UserInputForm.vue";
export default {
  components: {
    UserInputForm
  },
  data() {
    return {
      user: defaultUser() // { email: '', name: '', ... }
    };
  },
  methods: {
    createUser() {
      console.log(this.user);
    }
  }
};
</script>

```

`.sync` 修飾子を付与すると、子コンポーネントから`this.$emit('update:email', $event.target.value)` で親に通知することができます。（親は `update:prop名`のイベントを監視するようになります。)
参照: https://jp.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A3%BE%E5%AD%90

```UserInputForm.vue
<template>
  <form @submit.prevent="$emit('send')">
    <label>
      メールアドレス
      <input
        type="text"
        :value="email"
        @input="$emit('update:email', $event.target.value)"
      />
    </label>
    <label>
      名前
      <input
        type="text"
        :value="name"
        @input="$emit('update:name', $event.target.value)"
      />
    </label>
    <button type="submit" @click="$emit('submit')">登録</button>
  </form>
</template>

<script>
export default {
  props: {
    email: {
      type: String,
      default: ""
    },
    name: {
      type: String,
      default: ""
    }
  }
};
</script>
```

# 👍 (より短いコードにする) `.sync` 修飾子にオブジェクトを渡す

親から子にオブジェクトを渡したい場合、`.sync` 修飾子にオブジェクトを渡すと、子コンポーネントのpropsで値が展開されます。

```views/Parent.vue
<template>
  <div>
    <user-input-form v-bind.sync="user" @submit="createUser" />
  </div>
</template>
```

# まとめ
子コンポーネントにはフォームに必要な全量のpropsを記載する必要がありますが、安全に値を渡し、子から親に通知するために必要な作業と考えると良いでしょう。

`.sync` 修飾子、いかがでしたでしょうか？

今回のサンプルコードは、以下にホストしていますのでご活用ください。
https://github.com/harhogefoo/vue-sync-modifier-sample


Happy Coding! [@shino_tp](https://twitter.com/shino_tp)