ReactでshouldComponentUpdateを使ったチューニングの効果と注意どころ


GitHubはこちら

背景

Reactが流行ってる理由として、
Fluxやもともとのフレームワークの用法からデータフローが単方向へと簡略化され、速度面ではチューニングしたDOMを直接触る従来の方式には敵わないが、遅くなる事はないし何よりシンプルな方向へとバイアスがかかる恩恵がでかい、というのが自分の中での認識となっています。

ただ、速度は速い方が嬉しいので、フレームワークのベンチマーク比較プロジェクトに、shouldComponentUpdateを実装して、必要時にだけrenderが呼ばれるようにチューニングをしてどれだけ効果があるのかを検証してみた、というのが今回の記事です。

プロジェクトはこちらの記事のをForkして利用しています。

計測結果

上から、

  • Backboneの部分的再描画、
  • Backboneの全再描画
  • ReactのVirtualDOM全再描画、
  • ReactのVirtualDOM部分再描画(shouldComponentUpdateの手動チューニング)、
  • ReactのVirtualDOM部分再描画(shouldComponentUpdateチューニングに自作の汎用Mixinの利用)

となっています、shouldComponentUpdateの実装はかなり効果があるものと言えます。

shouldComponentUpdateで比較時の注意どころ

test.js
  shouldComponentUpdate: function(nextProps, nextState) {
    if (差分があった) {
      return true;
    }
    else {
      return false;
    }
  }

基本的に、やる事と言えばメンバのthis.props、this.stateと、引数のnextProps、nextStateとで差異があればtrueを返すだけで良いのですが、愚直にメンバの比較を羅列するのはこのメソッドのメンテナンスコストがかなり高くなってしまいます(効果は高いですが)。

また、シャローコピーされた参照型がプロパティに含まれている場合は、比較しても常にtrueが返ってしまい、誤動作の原因となってしまいます。

ng.js
  this.state.shallowedInstance === nextState.shallowedInstance; // FIXME : 常にtrue

全てのプロパティが値型なのであれば、PureRenderMixinアドオンが提供されているため、それを使うことが出来ますが、参照型の場合は別の手を考える必要があります。

メンテナンスコストを緩和する今回の対策

今回の対応方法としては、公式でも使用を推奨しているimmutable-jsを使い、Mixinとして使えるようにして、shouldComponentUpdateを使った時のメンテナンスコストを下げれるように図ってみました、速度は計測結果の一番下です、場合にもよりますが、効果はある方なんじゃないかと思います。

mixin.js
var Immutable = require('immutable');
var Mixin = {
  prevPropsMap: undefined,
  prevStateMap: undefined,

  getInitialState: function() {
    this.prevPropsMap = Immutable.Map();
    this.prevStateMap = Immutable.Map();

    return {};
  },

  shouldComponentUpdate: function(nextProps, nextState) {
    var nextPropsMap = Immutable.Map(nextProps);
    var nextStateMap = Immutable.Map(nextState);

    if (!Immutable.is(nextPropsMap, this.prevPropsMap) ||
        !Immutable.is(nextStateMap, this.prevStateMap)) {

      this.prevPropsMap = nextPropsMap;
      this.prevStateMap = nextStateMap;
      return true;
    }

    return false;
  }
};

使い方は、mixinsにこちらのmixinオブジェクトを指定して使います。
ちなみにGitHubのプロジェクトにもソースあります
(todomvc/react3/js/components/ShouldComponentUpdateMixin.react.js)。

参考