props
を直接v-model
に指定したらハマったのでメモ
環境
- vue 2.6.11
- JavaScript
何がしたかったか
子コンポーネントのクリックイベントで、選択された値を親コンポーネント経由で
別の子コンポーネントに渡したかった。
図にすると以下のような形。
もっと詳細に説明すると、カレンダーアプリを作っていて、以下のような構成にしていた。
コンポーネント名 | 概要 |
---|---|
componentA | 予定登録用のモーダルダイアログ |
componentB | カレンダーの実体を持つコンポーネント(月表示用ビュー) componentCに直接記述していないのは、タブで月・週・日表示を 切り替えたかったため |
componentC | カレンダーページ |
componentAで選んだ日付をcomponentC経由でcomponentBの初期値に設定したかった。
ここで問題になったのは、componentC -> componentBへのデータの渡し方
ダメな例
※componentA
のソースは省略しています。
propsをdataの初期値に指定
var componentB = {
props: {
'initDateString': String
},
data() {
return {
// ① propsで受け取った値をdataに代入
startDate: this.initDateString
}
},
template: `
<div>
<input type="date" v-model="startDate">
</div>
`
}
var componentC = {
components: {
componentA,
componentB
},
data() {
return {
selectedDate: null
}
},
methods: {
selected(value) {
this.selectedDate = value
}
},
template: `
<div>
<component-a @click="selected"></component-a>
<component-b :init-date-string="selectedDate"></component-b>
</div>
`
}
これがうまく行かなかったのは、以下が原因。
ソース中①
data
にprops
を代入しても、dataとpropsが関連付けられるわけではないため、
2回目以降のpropsの変更は、dataには反映されない
componentC -> Bに渡している値の初期値がnull
なので、
componentBの初期値としては何も設定されていない状態から変更されることがない。
propsをcomputed経由でdataの初期値に指定
componentCは変更なしのため省略
var componentB = {
props: {
'initDateString': String
},
data() {
return {
// ① computed経由で初期値を設定
startDate: this.dateString
}
},
template: `
<div>
<input type="date" v-model="startDate">
</div>
`,
computed: {
dateString() {
return this.initDateString
}
}
}
これがうまく行かなかったのは以下が原因
ソース中①
そもそもdata
への初期値代入の処理が通るのはインスタンス生成時の1回のみ
なのでcomputed
から返される値にはprops
の変更が反映されるが、
そのcomputedを呼び出す処理が最初しか通らないので、先程と同様の状態。
解決方法
computedにgetter,setterを用意し、v-modelに設定する
componentCは変更なしのため省略
var componentB = {
props: {
'initDateString': String
},
data() {
return {
// ① dataでは初期値設定しない
startDate: null
}
},
template: `
<div>
<!-- ② computedをバインドする -->
<input type="date" v-model="bindStartDate">
</div>
`,
computed: {
bindStartDate: {
get() {
// ③ 初期表示にのみ利用
// setterで別プロパティを指定しているので、その後DOMの値と連動しない
// ここはちょっと特殊なことしているかも
return this.initDateString
},
set(value) {
// ④ 実際に保存に利用するプロパティにセットする
// そもそもpropsを子コンポーネントで書き換えるとエラーになる
// もし書き換えたければ、this.$emit('change', value)で
// 親コンポーネントで書き換えて再度propsで渡し直す
this.startDate = value
}
}
}
}
↑はwatch
使ってもできるみたい。
省略しているが、実際のソースのdata
にはもう1つendDate
があって、
全く同じこと(setterでセットするのがendDate
なだけ)しているので、
この場合はwatch使ったほうがシンプルだったかも?
というか、このつくり自体あまり良くないのかな、、、? ちょっと複雑になってしまったし。
↓参考にさせていただきました。
https://ics.media/entry/200716/
https://qiita.com/simezi9/items/c27d69f17d2d08722b3a