仮想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
  }
})

  • 初期画面

f:id:gallard316:20210112143841p:plain


  • Add 10(age)ボタンを3回押したので50になった

f:id:gallard316:20210112143941p:plain


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()
      }
  }
})

  • それぞれに文字を打ってみる

f:id:gallard316:20210112153035p:plain


  • 削除ボタンを押すとズレる

f:id:gallard316:20210112153125p:plain


なぜこうなるのかというと先ほどの引用にあるこれが原因

「キーがない場合、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属性をつけることにより意図したように先頭が削除されました。

f:id:gallard316:20210112153919p:plain

key属性については

  • v-forでは必須
  • 一意になるようにする
    • 要素をkey属性にする場合、重複していると一意にならないのでオブジェクトでidを指定するなど
  • indexをkey属性にしない

ja.wikipedia.org

qiita.com

qiita.com

saneyukis.hatenablog.com

eh-career.com

jp.vuejs.org