Vue.js 2.3.0から実装された 「.sync 修飾子」が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のデータフローの思想に反してしまいます。
<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>
<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"
が今回のポイントです。
<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
<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で値が展開されます。
<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