ZOZOテクノロジーズのぱきお@pakzzzz0です。
普段はZOZOTOWNのバックエンドの開発を行っているのですが、今回は本業とは別に行っているフロントエンド周りの話です。
この記事はZOZOテクノロジーズ #5 Advent Calendar 2019 3日目の記事になります。
昨日は@satto_sannの英語論文をフォーマットして翻訳する方法でした。
▼その他アドベントカレンダーはこちら
ZOZOテクノロジーズ #1 Advent Calendar 2019
ZOZOテクノロジーズ #2 Advent Calendar 2019
ZOZOテクノロジーズ #3 Advent Calendar 2019
ZOZOテクノロジーズ #4 Advent Calendar 2019
背景
開発時にArray[Object]をv-forでレンダリングした際に、その中の1要素をバインドしたv-checkboxの動作に対して、選択の状態を持たせたオブジェクトのプロパティの変更がwatchで検知されなかったので、解決までの道のりをメモします。
やりたかったこと
watch or computed でArray of Objectな変数を監視して、変更を検出したい
環境
パッケージ | バージョン |
---|---|
vue | 2.6.10 |
vuetify | 1.5.16 |
Laravel-mix | 4.0.7 |
状況
data(){
return {
ObjectToWatch:[{id:1, hoge: true, huga: '100'}, {id:2, hoge: false, huga: '500'}],
ObjectProcessed: [],
}
},
watch:{
ObjectToWatch:{
handler(after, before){
this.ObjectProcessed = after.filter(function(val){
if(val.hoge){
return true;
}else{
return false;
}
}))
}
}
}
<div v-for="obj in ObjectProcessed" :key="obj.id">
<v-checkbox v-model="obj.hoge"/>{{obj.huga}}
</div>
上記例でv-checkboxの状態を変更しても反映されないobjのwatchプロパティが働かないことが全てのはじまりでした。
これまでv-bindで9割方やりたいことができていたため少し戸惑いつつも原因を追っていくことに
現象を追っていく
deep:trueとコンソール出力設定
watchメソッドでObjectToWatchを監視できてるかを確認するためにまずコンソール出力を噛ませました。
watch:{
ObjectToWatch:{
handler(after, before){
this.ObjectProcessed = after.filter(function(val){
if(val.hoge){
return true;
}else{
return false;
}
}))
console.log(this.ObjectProcessed)
},
deep:true
}
}
上記の時点で、Objectの監視には deep:true
オプションが必要とのドキュメントを見つけたので、deep:trueオプションも追加してみるも、まだプロパティの変更が検知できず
テンプレ側の値の変更メソッドと反映する値を変更
こちらの記事を参考に値変更時のメソッドとcheckedの裏側の値の持ち方を変更してみることに
onChechboxChange(checkboxId){
let index = this.ObjectProcessed.findIndex(item => item.id == checkboxId);
this.ObjectProcessed[index].hoge = !this.ObjectProcessed[index].hoge;
console.log(this.ObjectProcessed)
}
<div
v-for="obj in ObjectProcessed"
:key="obj.id"
:input-value="obj.hoge"
@change="onChechboxChange(obj.id)"
>
<v-checkbox v-model="obj.hoge"/>{{obj.huga}}
</div>
```
この時点で、console上ではonCheckboxChange内でObjectProcessed[index].hogeのtrue,falseが変更されていることは確認できましたが、watchメソッドでの算出はいまだにできず。
そこでテンプレ側にObjectProcessedをそのまま出力してみることに。
```html:templade
{{ObjectProcessed}}
<div
v-for="obj in ObjectProcessed"
:key="obj.id"
:input-value="obj.hoge"
@change="onChechboxChange(obj.id)"
>
<v-checkbox v-model="obj.hoge"/>{{obj.huga}}
</div>
```
すると、consoleでの出力とは裏腹に描画されている内容は変わっていないことが発覚、またwatchでのコンソール出力もされないことから反映できていないことを裏付けていました。
## 解決への糸口
[こちら](https://stackoverflow.com/questions/51624837/deep-watch-in-not-working-on-object-vue)のページを参考に、公式の[ListRendering](https://vuejs.org/v2/guide/list.html#Array-Change-Detection)のページをみると以下の記述がありました
>Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
また、サンプルとして以下のコードが紹介されていました。
```javascript:spliceを用いての実装
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
```
## 解決方法
これを参考に実装を変更。
```javascript:method
onChechboxChange(checkboxId){
let index = this.ObjectProcessed.findIndex(item => item.id == checkboxId);
let objToChange = this.ObjectProcessed[index];
objToChange.hoge = !objToChange.hoge;
this.ObjectProcessed.splice(index, 1, objToChange)
}
```
この実装に変更することでArrayの要素が変わったことを検知して無事にwatchが動作しました!
## 結論
Vue.jsのwatchプロパティにArray[Object]の変更を認識させるにはオブジェクトの要素を書き換えるのではなく、オブジェクトそのものの置き換えが必要。
明日は再び[@satto_sann](https://qiita.com/satto_sann)による「Alexaのアカウントリンクをデバッグする方法」です。