公式ページおよび結論
2.3.0から新規追加
“双方向バインディング”がプロパティに対して必要な場合もあります。残念ながら、本当の双方向バインディングはメンテナンスの問題を引き起こす可能性があります。子コンポーネントは親でも子でもその変更元が明らかでなくても親を変更させることができるからです。
このため代わりに update:myPropName というパターンでイベントを発火させる事をお薦めします。例えば title というプロパティを持つ仮のコンポーネントがあった場合、意図的に新しい値を割り当てる事ができます
実装例
Vuetify(1.5、ドキュメント比較するとおそらくVuetify2.x以降でも動くと思う)のdate-pickerを用いて妙に長く書かないといけないdate-pickerをコンポーネント化する。
※以下実装例はNuxt.jsの書き方で記述する。他Vue.js関連フレームワークであっても、プロパティなどの考え方は一緒だと考える。
親側
<template>
<!-- 子であるdatepickerを設定する。このとき.syncを付けておくのが鍵。 -->
<datepicker :date.sync="date"></datepicker>
<!-- 他、自作コンポーネントdatepickerの配置および呼び出しなど色々あるけど中略 -->
</template>
<script>
export default {
data() {
return {
// 親側のdate。こちらに反映される
date:''
}
}
}
</script>
子側
<!-- 殆どはvuetify公式ページにある
「テキスト欄クリックでカレンダーがポップアップし、
日付押下すると日付がテキスト欄に適用される部品」 -->
<template>
<v-menu
ref="datetime"
v-model="datetime"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on }">
<!-- ここでのv-modelには親からのプロパティは直接指定しない。 -->
<v-text-field
autocomplete="off"
v-model="_date"
prepend-icon="event"
v-on="on"
readonly
clearable
></v-text-field>
</template>
<v-date-picker
v-model="_date"
locale="ja-jp"
:day-format="nowdate => new Date(nowdate).getDate()"
no-title
scrollable
@input="datetime = false"
>
</v-date-picker>
</v-menu>
</template>
<style scoped></style>
<script>
// emit時のupdate呼び出し文字列
const emitUpdate = 'update:'
// 親からのプロパティ名集
const propName= {
date:'date'
}
export default {
// 親からもらうプロパティ名を指定
props: [propName.date],
data() {
return {
datetime: false
}
},
computed: {
// 子コンポーネント側の算出プロパティをget,setで制御
_date: {
// 親からプロパティを貰ったら反映
get: function() {
return this.date
},
// 子で値が変更されたなら、その代わりにemit発火によって親へとupdate
// update対象をプロパティごとに指定できるため、複数プロパティにも対応可能
set: function(value) {
this.$emit(emitUpdate + propName.date, value)
}
}
}
}
</script>
子側で色々書いておけば、親側で使う時はコンポーネント呼び出しと引数設定をしておけば子側で処理した値を引っ張ってこれる。まさに理想。
結論にいたるまでの$emitの基本と面倒なところ
vue.jsにおいて、本当の双方向バインディングはメンテナンスの問題を引き起こす可能性があるため推奨されておらず、上手く動かないことが多い。
自身の認識では、emitは子コンポーネントから親コンポーネントへと「〇〇イベントを実行してください」といった発火イベントを起こすことができると考えている。
よっては基本的には親側のメソッドでイベント内容を記述する必要がある。
1:親が子へとプロパティを渡す(そのプロパティ自体は親コンポーネントのプロパティであるため、他コンポーネントが変更を行ってしまっては、何処で変更されたかといった情報が読み取れなくなってしまう)
2:子が処理を行った後、emitによって親へと「〇〇というイベントが親にはありますよね、実行してください」と発火させる
3:親が自身のイベントを実行し、親プロパティへの反映や親側の処理を起動させる
ただ、親子間での単純なプロパティ変更である場合、上記のような処理記述は面倒でしかないし、再利用時のバグになりかねない。
.syncの機能はそこらへんを簡略化してくれていると考えている。