v-for内のv-checkboxの値をwatchプロパティで検知したい

ZOZOテクノロジーズのぱきお[@pakzzzz0](https://twitter.com/pakzzzz0)です。
普段はZOZOTOWNのバックエンドの開発を行っているのですが、今回は本業とは別に行っているフロントエンド周りの話です。

この記事は[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5) 3日目の記事になります。
昨日は[@satto_sann](https://qiita.com/satto_sann)の[英語論文をフォーマットして翻訳する方法](https://qiita.com/satto_sann/items/7d4c8c6ec6fd761c4e54)でした。

▼その他アドベントカレンダーはこちら
[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech)
[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
[ZOZOテクノロジーズ #3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
[ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4)


## 背景

開発時に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 |

## 状況
```javascript
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;
                }
            }))
        }
    }
}
```
```html:template
<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を監視できてるかを確認するためにまずコンソール出力を噛ませました。

```javascript:watcher
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` オプションが必要との[ドキュメント](https://jp.vuejs.org/v2/api/#_carbonads_projs)を見つけたので、deep:trueオプションも追加してみるも、まだプロパティの変更が検知できず


### テンプレ側の値の変更メソッドと反映する値を変更
[こちら](https://uncle-javascript.com/vuetify-checkbox)の記事を参考に値変更時のメソッドとcheckedの裏側の値の持ち方を変更してみることに

```javascript:method
onChechboxChange(checkboxId){
    let index = this.ObjectProcessed.findIndex(item => item.id == checkboxId);
    this.ObjectProcessed[index].hoge = !this.ObjectProcessed[index].hoge;
    console.log(this.ObjectProcessed)
}
```
````html:tamplate
<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のアカウントリンクをデバッグする方法」です。