Help us understand the problem. What is going on with this article?

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

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

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 オプションが必要とのドキュメントを見つけたので、deep:trueオプションも追加してみるも、まだプロパティの変更が検知できず

テンプレ側の値の変更メソッドと反映する値を変更

こちらの記事を参考に値変更時のメソッドとcheckedの裏側の値の持ち方を変更してみることに

method
onChechboxChange(checkboxId){
    let index = this.ObjectProcessed.findIndex(item => item.id == checkboxId);
    this.ObjectProcessed[index].hoge = !this.ObjectProcessed[index].hoge;
    console.log(this.ObjectProcessed)
}
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をそのまま出力してみることに。

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でのコンソール出力もされないことから反映できていないことを裏付けていました。

解決への糸口

こちらのページを参考に、公式のListRenderingのページをみると以下の記述がありました

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

また、サンプルとして以下のコードが紹介されていました。

spliceを用いての実装
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

解決方法 

これを参考に実装を変更。

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による「Alexaのアカウントリンクをデバッグする方法」です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away