React製社内ツールの高速化事例

Indeed 有田夏洋

2016/7/21

自己紹介

チームでのReactの使われ方

React製社内ツール

React製社内ツール イメージ

Alias
▽Bulk Alias
Action 2
▽Bulk Action
会社名 情報1 情報2 ...
▽Alias ▽Action A Apple hoge
▽Alias ▽None Banana fuga
▽None ▽None Chocolate piyo
▽None ▽Action B Donut uhyo
... ... ... ...

[Fetch Data] [Submit Actions]

社内ツールコード

const Tool = React.createClass({
  getInitialState(){
    return {list: []}; }, //各行の情報
  render: () => [
    (updateを呼ぶ操作パネル),
    <Table list={this.state.list} update={this.update}/>],
  update() {(listステートを更新する処理)} });
const Table = React.createClass({
  componentWillReceiveProps() {
    this.setState({list: this.props.list})},
  render(){(listステートを元にテーブル描画)} }); // 操作によってはupdateを呼ぶ

社内ツールの速度問題

  1. セルのrenderが呼ばれる回数が多い
    • 1000行 × 10列,~1秒
  2. 親(Tool)のstateが変わってrenderされやすく,その都度Tableのrenderが呼ばれる
    • 明らかにデータが変わっていないなら,Tableのrenderを呼びたくない

render回数問題解決

振り返り: Reactの特徴

shouldComponentUpdate

次のpropとstateが渡されるので,現在のそれらと比較して,renderする必要があればtrueを返す

shouldComponentUpdate(nextProps,nextState){
    return (renderを呼ぶべきか否か);
}

→どう判定する?

案1: PureRenderMixin (shallow equal)

PureRenderMixin 概念コード

shouldComponentUpdate(nextProps, nextState){
  for (const key in nextProps){
    if (nextProps[key] != this.props[key])
      return true; }
  for (const key in nextState){
    if (nextState[key] != this.state[key])
      return true; }
  return false;
}

しかしlistの更新のされ方が

const Tool = React.createClass({update() {// i番目の要素のアクションIDを変更
        this.state.list[i].actionId = "invalidate";
        this.setState({list: this.state.list});},
});

案2: deep equal

こんなライブラリがほしい…

this.setState({list: this.state.list}); 

案3: Immutable.js

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
* 変更をすれば必ず参照が変わる == 参照が同じなら変わっていない

Immutable.jsへの書き換え

stateのlistをImmutableに






const newList = Immutable.fromJS(jsList); // 初期化
.. = this.state.list.push(elem)}); // 追加
this.state.list.set(index, elem)}); // 変更
.. = this.state.list.update(index, (elem)=>{ // 深い変更
    return elem.set("actionId", "invalidate")})});
.. = this.state.list.withMutation((list)=>{ // 内側で破壊的変更
    list.set(10, elem);
    list.remove(0); })});

setState({list: newList});

TableのshouldComponentUpdate

const Table = React.createClass({
  componentWillReceiveProps() {
    this.setState({list: this.props.list})},
  render(){(listステートを元にテーブル描画)},
  // 追加
  shouldComponentUpdate(nextProps, nextState){
    return this.state.list != nextState.list
  } });

結果

おまけ: ReduxとImmutable.js

まとめ