React.PureComponentの概要

Reactでクラスベースのコンポーネントを利用する場合は、通常React.Componentを継承します。これには他にもReact.PureComponentというクラスを利用することができます。

今回はこの"Pure"なコンポーネントに触れてみます。

サンプル

以下のようなコードを想定します。Demoも用意しました。

import React from "react";
import ReactDOM from "react-dom";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: ["hello"] };
  }

  componentDidMount() {
    setInterval(() => {
      const message = [...this.state.message];
      message.push("New Message");
      this.setState({ message });
    }, 1000);
  }

  render() {
    return (
      <div>
        <MessageList message={this.state.message} />
      </div>
    );
  }
}

class MessageList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.message.map((m, i) => <Message key={i} message={m} />)}
      </ul>
    );
  }
}

class Message extends React.Component {
  render() {
    console.log("Add Message");
    return <li>{this.props.message}</li>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

componentDidMount内で定義したsetIntervalにより、毎秒新しい"New Message"という文字列をmessage stateに追加しています。stateが常時更新され、結果は下記のようになります。

ただし、コンソールを見てみると恐ろしいことが起きています。

messageは毎秒追加されるためMessageコンポーネントも再レンダリングが行われるのですが、毎秒message配列全件分が再レンダリングされています。

React Developer ToolのHighlight Updatesでも、それが確認できます。

追加されたコンポーネントだけレンダリングできればいいはずです。変化していないデータを更新する必要はありません。これはパフォーマンスに影響するでしょう。

PureComponentを使う

これを改善する一番簡単な方法がReact.PureComponentです。MessageコンポーネントをPureComonentで書き換えるだけです。

class Message extends React.PureComponent {
  render() {
    console.log("Add Message");
    return <li>{this.props.message}</li>;
  }
}

コンソールを見てみると、先程と異なるのが確認できます。

下記はHighlight Updatesの結果です。

PureComonentはprops及びstateの変更を検出した場合のみレンダリングを行います。Messageコンポーネントではmessage propsの変更を察知し、必要分の更新を行うようになります。

何が機能しているか

通常、React.Componentのコンポーネントが保持するstate・propsの値が変化に応じてレンダリングを行うかどうかは、shouldComponentUpdateというメソッドで決められています。デフォルトでは、すべての変更に対して再レンダリングを行います。一般的にこの挙動を変える必要はありませんが、自身でレンダリングの条件をチューニングしたい場合は、このメソッドを使うことがあります。今回の例のようなケースでも利用が考えられます。

一方、React.PureComponentshouldComponentUpdateメソッドを持ちません。

React.ComponentはshouldComponentUpdateを実装していませんが、React.PureComponentは、propsとstateの浅い比較を実装しています。

浅い比較というのは、簡潔に述べるとオブジェクトの参照先が同じであれば等しいと見なすことです。参照が同じならば内部の状態が変更されても等価と見なすため、ネストされたオブジェクトの場合は予期せぬケースを招いてしまう可能性があります。しかし、処理に対するコストは少ないため、高速に動作します。

なぜパフォーマンスが上がるのか

何故必要部分だけレンダリングが行われるのか?

再レンダリングの条件がPureComponentに実装されているものに変わります。それが浅い比較です。また、ドキュメントには以下のように記載されています。

Reactコンポーネントのrenderメソッドが、同じpropsやstateで同じ結果をレンダリングする場合は、React.PureComponentを使用してパフォーマンスを向上させることができます。

ドキュメントの通りです。

React.PureComponentshouldComponentUpdataを変更し、コンポーネントに再レンダリングが必要かどうかを自動でチェックするロジックを追加します。これにより、stateやpropsの変更のあった箇所のみ検出し、renderメソッドを呼び出します。結果、余分なレンダリングが行われなくなるためパフォーマンスが上がるというわけです。例で言えば、毎秒単位追加される1件分のmessageだけがレンダリングされます。

いつ使うのか

PureComponentは銀の弾丸というわけではありません。先にも書いたように、オブジェクトの構造が深い場合は、浅い比較により変更が感知されず予想外の結果が生じる場合があります。抜け道としては、forceUpdatae()やImmutableなオブジェクトを使う方法があります。

また、親コンポーネントがPureComponentの場合、親のレンダリングがスキップされると子コンポーネントもレンダリングがされません。このため、子の再レンダリングが不要とわかっている場合のみ親をPureComponentにすることができます。

ここには記載していませんが、ハンドラ内やアロー演算子など注意点は他にもあり、使用に関して癖があります。基本的には子を持たないコンポーネント、もしくは単純な状態の場合でのみ利用するということになるかと思います。