LoginSignup
28

More than 5 years have passed since last update.

React-Virtualizedでレンダリングを効率化する

Last updated at Posted at 2019-02-16

React製のSPAのパフォーマンスチューニング実例を読んでいて、react-virtualizedというライブラリを試してみたくなりました。 結果としては、初回アクセスのレンダリング速度が4倍ほど速くなりました。

react-virtualizedの概要

Reactコンポーネントがたくさん組み込まれたページをレンダリングしようとすると、コンポーネントの数に比例してレンダリングに時間がかかってしまいます。コンポーネントが少なければ問題はないのですが、スクロールを駆使するようなWebサイトではレンダリングがボトルネックになります。react-virtualizedを使うと、 ブラウザの画面に表示されている領域に存在するコンポーネントのみをレンダリング してくれます。

GitHub
https://github.com/bvaughn/react-virtualized

検証のため作ったアプリ

自分の手元にはボトルネックになるほどのコンポーネント数になるような題材がなかったので、ポケモンを題材にアプリを作ってみました。最近のポケモンは全部で 809匹 もいるようなので、それだけの数のコンポーネントがあれば、react-virtualizedの検証ができると思いました。

  • ポケモン全809匹を表示する
  • 名前の検索ができる
  • タイプで検索ができる

デモサイトを用意してみました。

ポケモン一覧

結果

FMPの数値

react-virtualizedを使うと、初回アクセス時のレンダリング速度が上がりました。

  • react-virtualizedを使わない場合
    • 3449.4ms
  • react-virtualizedを使った場合
    • 799.8ms

画面キャプチャ

画面キャプチャも取ってみました。1/4も時間短縮していると、目に見えて違いがわかる感じです。

react-virtualizedを使わない場合
リンク
キャッシュクリアしてからロードしてます。描画に3.5秒くらいかかっています。
before.gif

react-virtualizedを使った場合
リンク
こちらは1秒以内で初回レンダリングが完了しています。
after.gif

ちなみに、使ってない版はすべてのポケモンコンポーネントを描画するので、サイトを開いてからずーっと画像取得のリクエストを飛ばしています。一方、react-virtualizedを使うと、初回に生成されるポケモンコンポーネントはごくわずかなので、画像取得のリクエストは少ないです。ただし、スクロールをはじめると、画像リクエストが発生します。

ソースコード

react-virtualizedには様々な機能がありますが、今回使用したのはWindowScrollerというコンポーネントです。
https://bvaughn.github.io/react-virtualized/#/components/WindowScroller

WindowScrollerを使って、Material-UIのCardをグリッド表示します。react-virtualized自体にはリスト表示する機能はありますが、グリッド表示する機能はないです。なので1行に複数のコンポーネントを組み込む計算処理を自前で実装する必要があります。

render() {
  const { classes, pokemons } = this.props;
  return (
    <div className={classes.root}>
      <WindowScroller>
        {({ width, height, isScrolling, registerChild, scrollTop }) => {
          // 1行あたりに何匹を描画するかwidthから計算
          const itemsPerRow = Math.max(1, Math.floor(width / CARD_WIDTH) - 1);
          // 全体で何行必要か計算
          const rowCount = Math.ceil(pokemons.length / itemsPerRow);

          return (
            <React.Fragment>
              <div ref={registerChild} className={classes.cardArea}>
                <List
                  autoHeight
                  width={width}
                  height={height}
                  isScrolling={isScrolling}
                  scrollTop={scrollTop}
                  rowCount={rowCount}
                  rowHeight={CARD_HEIGHT + ROW_HEIGHT_MARGIN}
                  rowRenderer={({ index, key, style }) => {
                    const items = []; // 1行に表示するコンポーネントを格納するリスト
                    // from toを計算して、itemsへコンポーネントをappendする
                    const fromIndex = index * itemsPerRow;
                    const toIndex = Math.min(fromIndex + itemsPerRow, pokemons.length);
                    for (let i = fromIndex; i < toIndex; i++) {
                      items.push(<PokeCard key={i} pokemon={pokemons[i]} />);
                    }
                    // グリッドの最終行を左寄せにするための処理。何も表示しないコンポーネントをappend
                    const emptySize = itemsPerRow - items.length;
                    for (let i = 0; i < emptySize; i++) {
                      items.push(<PokeCard key={i + toIndex} empty />);
                    }
                    return (
                      <div className={classes.row} key={key} style={style}>
                        {items}
                      </div>
                    );
                  }}
                />
              </div>
            </React.Fragment>
          );
        }}
      </WindowScroller>
    </div>
  );
}

トラブったところ

最初は上手く動いていたように見えたのですが、1つ問題がありました。スクロールを行っている途中で横スクロールバーが表示・非表示を繰り返すため、画面がちらつきました。これはissueにも上がっていました。
https://github.com/bvaughn/react-virtualized/issues/955

なので暫定対応として、横スクロールを常にonにするcssを追記することにしました。

ソースコード

https://github.com/ksakiyama/pokemon-react-virtualized
もっときれいに書ける方法があれば教えてください。

まとめ

  • react-virtualizedを使うと、画面に表示されないエリアのコンポーネント描画をしなくて良い
    • 初回のレンダリング速度が上がる。使わない場合と比べて1/4の速度改善
    • スクロールして画面表示される直前のタイミングでDOM生成して、効率的
  • react-virtualizedにはグリッド表示の機能はないので、自前で計算処理をする必要がある

参考

react-virtualized
AirNYT: React-Virtualized + Material-UI Cards for Fast Lists

ポケモン
alik0211/pokedex
Pokemon.json
すばらしきポケモンエコシステム⚡️

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28