仮想DOMとは
Vue.jsを触ると仮想DOMという概念が出てくるので簡単に整理してみる。
概要
効率良くDOMを操作するにはどうすれば良いか?
例えば、以下のようなコードがありage
を+10するボタン
を3回押してみるとage
は50になる
この場合のDOMの変化は<p>age: {{ age }}</p>
のみである、他は変化していない
ということは変化した部分のみを差し替えればいいのではないか!!!
そこで登場するにが仮想DOMである。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="example-1"> <button v-on:click="counter += 1">Add 1(num)</button> <p>The button above has been clicked {{ counter }} times.</p> <p>name:{{ name }}</p> <p>age: {{ age }}</p> <button @click="age += 10">Add 10(age)</button> </div>
new Vue({ el: '#example-1', data: { counter: 0, name: "太郎", age: 20 } })
- 初期画面
Add 10(age)
ボタンを3回押したので50になった
WikipediaのReactより
もう1つの注目すべき機能は、仮想DOMの使用である。Reactでは仮想DOMとしてメモリ上にDOMの状態をキャッシュしておき、仮想DOMに差分が発生した場合にのみ差分を計算し、実際のDOMに差分のみを反映させることにより効率的な描画を実現している
実際の動き
仮想DOMを二つ用意
- 一方の仮想DOMをJavascriptで操作(一般的にリアルDOMを操作するより速い)
- 変更前後の仮想DOMの差分を比較
- 差分だけをリアルDOMに反映
- 反映されたリアルDOMをブラウザがレンダリング
ということで最終的にリアルDOMを操作するのですが、通常、リアルDOMを操作する場合はリアルDOMが変更されるたびにブラウザがHTMLを解析してレンダリングするのでコストが高いです。
結局、結論としては「設計と速度が両立させるため」。
Vueを触る際の注意点
一点だけパフォーマンス上の理由で知らないといけないものがあります。
key 特別属性は、主に古いリストの代わりにノードの新しいリストを差分算出する VNode を識別するために Vue の仮想 DOM アルゴリズムに対するヒントとして使用されます。キーがない場合、Vue は要素の移動を最小限に抑えるアルゴリズムを使用し、可能な限りその場で同じタイプの要素にパッチ適用/再利用しようとします。
キーがある場合は、キーの順序の変化に基づいて要素を並べ替え、そして、もはや存在しないキーを持つ要素は常に削除/破棄されます。
同じ共通の親を持つ子は、一意なキーを持っていなければなりません。重複するキーはエラーを描画する原因になります。
- 例を使って実際の動きを見てみる
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="example-2"> <ul> <div v-for="fruit in fruits"> <p>{{ fruit }}</p> <input type="text"> </div> </ul> <button @click="remove">先頭を削除</button> </div>
new Vue({ el: '#example-2', data: { fruits: ["apple", "orange", "grape"] }, methods: { remove: function() { this.fruits.shift() } } })
- それぞれに文字を打ってみる
- 削除ボタンを押すとズレる
なぜこうなるのかというと先ほどの引用にあるこれが原因
「キーがない場合、Vue は要素の移動を最小限に抑えるアルゴリズムを使用し、可能な限りその場で同じタイプの要素にパッチ適用/再利用しようとします」
この予期せぬバグに対処するためにkey属性を指定します
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="example-2"> <ul> <div v-for="fruit in fruits" :key="fruit"> <p>{{ fruit }}</p> <input type="text"> </div> </ul> <button @click="remove">先頭を削除</button> </div>
key属性をつけることにより意図したように先頭が削除されました。
key属性については
- v-forでは必須
- 一意になるようにする
- 要素をkey属性にする場合、重複していると一意にならないのでオブジェクトでidを指定するなど
- indexをkey属性にしない