Vue.jsで大量の配列要素を滑らかに描画
この記事は最終更新日から1年以上が経過しています。
芽萌丸プログラミング部@programming
投稿日 2019/12/23
更新日 2019/12/23 ✏

Vue.jsで大量の配列要素を滑らかに描画

Vue.jsで大量の配列データの要素を滑らかにレンダリングする方法です。

Vueモデルで管理され画面表示にも使われている配列データにforループ等で要素データを投入すると、Vueにレンダリング処理が移るまでイベントループがブロッキングされてしまい、画面がプチフリーズしてしまいます。 これを防ぐ方法は、setTimeout等で小まめにイベントループを進めながら描画処理にも処理の機会を提供してあげることです。

目次

サンプルコード

以下はサンプルコードです。

...
<head>
  ...
  <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</head>

...

<div id="content">
  <button @click="badRender()">UIがプチフリーズするレンダリング</button>
  <button @click="goodRender()">滑らかなレンダリング(ただし遅い)</button>
  <button @click="betterRender()">速くて滑らかなレンダリング</button>
  <div v-for="item in list">
    <div v-text="item"></div>
  </div>
</div>

<script type="text/javascript">
new Vue({
  el: "#content",
  data: {
    list: [],
  },
  methods: {
    getDummyList() {
      const list = [];
      for (let len = 9999, i = 0; i < len; i++) {
        list.push(i);
      }
      return list;
    },
    // anti-Pattern! this logic causes ui blocking:
    badRender() {
      const self = this;
      self.list = [];
      const list = self.getDummyList();
      for (let len = list.length, i = 0; i < len; i++) {
        self.list.push(list[i]);
      }
      console.log("rendering done!");
    },
    // smooth (but slow) rendering:
    goodRender() {
      const self = this;
      self.list = [];
      const list = self.getDummyList();
      const ite = function*() {
        // NOTE: 1アイテムづつsetTimeoutでレンダリング
        for (let len = list.length, i = 0; i < len; i++) {
          yield setTimeout(() => {
            const item = list[i]; // Get items one by one
            self.list.push(item);
            ite.next();
          });
        }
        console.log("rendering done!");
      }();
      ite.next();
    },
    // smooth & faster rendering:
    betterRender() {
      const self = this;
      self.list = [];
      const list = self.getDummyList();
      const ite = function*() {
        // NOTE: 100アイテムづつsetTimeoutでレンダリング
        while (true) {
          const items = list.splice(0, 100); // Get items 100 by 100
          if (items.length <= 0) break;
          yield setTimeout(() => {
            for (let len = items.length, i = 0; i < len; i++) {
              const item = items[i]
              self.list.push(item);
            }
            ite.next();
          });
        }
        console.log("rendering done!");
      }();
      ite.next();
    },
  }
});
</script>

サンプルコードレビュー

全てのボタンの処理内容は全て同じです。 ボタンを押下すると、1〜9999までのデータがVueデータlistにプッシュされ、画面にはその内容がレンダリングされます。 それぞれのボタンで異なるのは処理の仕方です。それぞれの処理の仕方はVueのmethodsに定義されています:

  1. badRender():アンチパターンです。アイテムを一つづつVueデータlistにプッシュしますが、次のイベントタイミングへ移る前にまとめてプッシュしているため、listのデータ量が多い場合にUIをプチフリーズさせてしまいます。
  2. goodRender(): この方法はアイテムを一つづつアイテムを取得し、その都度setTimeoutでイベントループを進めながらVueデータlistへプッシュしています。そのため、UIのプチフリーズを防ぎ、UIを滑らかに表示します。
  3. betterRender(): この方法はsetTimeoutでイベントループを進めながら100アイテムづつVueデータlistへプッシュしています。UIをそこそこ滑らかに表示しつつ、表示完了もgoodRender()より早めることができます。

1番目の方法は非推奨のアンチパターンです。大量データのレンダリングではUIがプチフリーズするためです。 2番目の方法はそこそこオススメですが、ただし表示が全て完了するのにかなり時間が掛かります。UIを滑らかに表示しつつ、表示も速く完了させたいなら3番目の方法(1番目と2番目の折衷案)がベストです。

以上です。


芽萌丸プログラミング部
芽萌丸プログラミング部@programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。記事は主に @TanakaSoftwareLab が担当。