コンポーネントを使って描画更新のコストを削減する

2018-03-04 #javascript  #vue.js 

Vue.jsのコンポーネントを使うメリットは再利用性だけじゃなくて再レンダリングの計算コスト削減もある、という話。 題材としてこんなUIを作ってみる。

  • 複数のアイテムをリスト表示する。
  • 各アイテムはそれぞれ複数のチェックボックスを持つ。
  • 各アイテムのチェックされた数を表示する。

コンポーネントを使わない例

まずはシンプルにコンポーネントを使わずに実装する。

リスト表示するアイテムは2つ(Alice, Bob)でそれぞれが3つのチェックボックス(a, b, c)を持つ。 チェック状態のv-modelは1つのオブジェクト(checkedList)で管理する。 チェック数はgetCountメソッドで取得し、内部でアイテム名をconsole.logしている。

<div id="app-no-comp">
  <ul
    v-for="person in people"
    :key="person.name">
    <li>Name: {{ person.name }}</li>
    <li>Checked: {{ getCount(person.name) }} </li>
    <li>
      <input
        type="checkbox"
        :value="'a'"
        v-model="checkedList[person.name]">a
      <input
        type="checkbox"
        :value="'b'"
        v-model="checkedList[person.name]">b
      <input
        type="checkbox"
        :value="'c'"
        v-model="checkedList[person.name]">c
    </li>
  </ul>
</div>
new Vue({
  el: '#app-no-comp',
  data: {
    checkedList: {Alice: [], Bob: []},
    people: [{name: 'Alice'}, {name: 'Bob'}]
  },
  methods: {
    getCount(name) {
      console.log(`getCount: ${name}`);
      return this.checkedList[name].length;
    }
  }
});

以下が実際の動作。

  • Name: {{ person.name }}
  • Checked: {{ getCount(person.name) }}
  • a b c


ブラウザのデベロッパーツールを開いてチェックボックスを操作してみると、毎回2つの出力がコンソールに現れるはずだ。

  • getCount: Alice
  • getCount: Bob

つまりチェック数を更新しなければならないのはAliceとBobどちらかだけなのに再レンダリングのタイミングでは両者の分のメソッドが実行されている。公式ドキュメントでも説明されているようにメソッドは結果がキャッシュされないからだ。

https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods

小規模なUIであればそれで問題ないだろうが、アイテム数が多い、コストの高いメソッドを実行するような場合はコストが無視できなくなるかもしれない。

コンポーネントを使った例

ということで各アイテムをコンポーネント化して再レンダリングの範囲を狭めることにする。

<div id="app-use-comp">
  <check-list
    v-for="person in people"
    :person="person"
    :key="person.name">
  </check-list>
</div>
Vue.component('check-list', {
  props: ['person'],
  template: `
     <ul>
     <li>Name: {{ person.name }}</li>
     <li>Checked: {{ getCount(person.name) }} </li>
     <li>
       <input
         type="checkbox"
         :value="'a'"
         v-model="checkedList">a
       <input
         type="checkbox"
         :value="'b'"
         v-model="checkedList">b
       <input
         type="checkbox"
         :value="'c'"
         v-model="checkedList">c
     </li>
   </ul>
  `,
  data() {
    return {
      checkedList: []
    };
  },
  methods: {
    getCount(name) {
      console.log(`getCount: ${this.person.name}`);
      return this.checkedList.length;
    }
  }
});

new Vue({
  el: '#app-use-comp',
  data: {
    people: [{ name: 'Alice' }, { name: 'Bob' }]
  }
});

実際の動作。今度はチェックボックスを操作した方のみ、コンソールに出力されるはずだ。