順を追って説明する。
まず次のようなData
部がある。
data: () => ({
obj: {
a: "A: OK",
}
}),
これをテンプレート部で「{{obj}}
」として表示してやると
{ "a": "A: OK" }
と表示されるはずだ。
mounted()に次のコードを追加する。
mounted(){
this.obj.b = 0
}
mounted
はここでは理解する必要はない。
DOM要素がマウントされ画面が描画され、再描画される準備が整ったところと思っておけば良い(たぶん)
つまり obj
に "b"
のキーを新規で追加したので
{ "a": "A: OK", "b": 0 }
となることを期待したコードだ。
しかし、実際は次の表示になる。
{ "a": "A: OK" }
これがリアクティブではない状態だ。初心者が躓く箇所である。
なぜ"b"
は描画されないのだろうか。
画面上はまだ"a"
しか表示されていない状態でvueのデバッガを見てみる。
obj
はどうなっているだろうか(デバッガの使い方が分からなければconsole.log(obj)
でもよい)
間違いなく"b":0
も追加されている。
しかし、画面には描画されていない。
これは公式の「リアクティブの探求」ページに記載されている。
Vue はプロパティの追加または削除を検出できません。Vue はインスタンスの初期化中に getter/setter の変換を行うため、全てのプロパティは Vue が変換してリアクティブにできるように data オブジェクトに存在しなければなりません
読んでもよくわからないと思う。英語を日本語に直したものなのでイマイチピンとこない。
要するに、初めにdata部で定義されていた物を除いて後からオブジェクトにキーを追加しても再描画されないよということである。
では、どうすればよいのだろうか。
深く考えずに次のコードを通してオブジェクトを更新してやれば良い。
mounted(){
//this.obj.b = 0
this.$set(this.obj, "b", 0)
}
this.$set(オブジェクト, キー名, 値)
このコードは this.obj.b = 0
と同じ意味合いでありながら、キー"b"
をリアクティブにすることが出来るコードだ。
要するにキーを直接追加するのではなく一旦Vue本体を通して更新してやることでVueがリアクティブなオブジェクトとして認識できるようになったと考えておけば良い。
「オブジェクトではなく配列をリアクティブにする」場合は配列に.splice()
を付けて更新してやれば良い。
基本はこれだけで終了だ。
ここからはリアクティブの「探求」
ちょっと別のコードで研究をしてみる。
次のコードにしたらどうなるだろうか。
mounted(){
this.obj.b = 0
this.obj.a = "A: Updated"
}
"b"
は非リアクティブ、"a"
はリアクティブだ。
結果は
{ "a": "A: Updated", "b": 0 }
どうやらリアクティブな"a"
が更新されたため、同じobj
内の"b"
の描画も更新された。
これはどうか。
mounted(){
this.obj.b = 0
this.obj.a = "A: Updated"
this.obj.b = 1
}
こんどは"b"
に0
を入れて"a"
を更新した後にさらに"b"
を更新している。
これまでを読めば"b"
はリアクティブではないので内部的には1
だが描画は0
なのではないかと思うが
{ "a": "A: Updated", "b": 1 }
しっかりと更新されている。
つまりmounted(){}
内がすべて実行された後に再描画される。
余談:これは一つの関数内で大量の再描画が必要そうなコードがあっても、纏めて1回更新にすることで無駄な描画負荷を減らすVueのアルゴリズムだ。
なら次はどうだろう。
<template>
<div id="app">
{{obj}}
{{uho}}
</div>
</template>
uho
オブジェクトを追加した。
data
部には先程と同じobjは記述してあるが、uho
オブジェクトは全く定義していない。
その上で
mounted(){
this.uho = {
a: "uho: ok"
}
}
uho.a
に"uho: ok"
を入れた。
実行結果は
{ "a": "A: OK" }
uho
は表示されない。当然だ。ここまで学習していればuho
が描画されない理由は分かるだろう。
では、次のコードをに変更する。
mounted(){
this.obj.a = "A: Updated" //リアクティブなobj.aも一緒に更新
this.uho = {
a: "uho: ok"
}
}
さて、どうなる。
{ "a": "A: Updated" } { "a": "uho: ok" }
なるほど、uho
オブジェクトも更新された。リアクティブな何かが更新されれば、同時に別のリアクティブじゃない物も再描画されるらしい。
この関係性を正しく理解していないと、予期せずリアクティブな物と非リアクティブな物を混在した場合に時々再描画されないバグや、コードを変えた途端に描画が意図しないものになる可能性がある。
以上の理由から初めからキー構造をdata部に列挙しておくのが重要ということが分かる。値は空 ''
等でよい。
この関係で直接DOMを操作するコードを使っている場合、同じファンクション内で再描画を明示的に待ってやるコード $nextTick()
が必要となるパターンもある。
これは高度なテクニックであるため詳しくは説明しないがシンプルで分かりやすいサイトがあったので外部リンクを載せておく。
Vue.js で表示に切り替えたDOMにフォーカスできないときは $nextTick を使う
実は度々この問題で頭を悩ませていた。今回これでクリアになったのでもう迷わないだろう。
Vueの少し難しい領域だが絶対に覚えておきたい項目だ。