[React] 描画速度を改善するには

 すぐにできる基本的な再描画を減らす方法についてまとめました。

Stateの影響範囲を小さくする


 基本中の基本ですが、コンポーネントにstateの影響を無駄に受けさせないことで描画回数を減らせます。

 かなり単純な例ですが、以下のようなカウントアップするボタンと表示のコンポーネントCountComponent、静的表示のSimpleComponentを例にします。

 それぞれのコンポーネントで再レンダリング走ったときにログ出力するようにしています。

 CountComponentはカウントStateを持っているコンポーネントです。その中に特に意味もなく静的なSimpleComponentがあります。

 この場合だとカウントが上がるたびに静的なSimpleComponentも再描画が走ってしまいます。

import { useState } from "react";

const SimpleComponent = () => {
  console.log("Render SimpleComponent");
  return (
    <p
      style={{
        width: "100%",
        backgroundColor: "black",
        color: "white",
      }}
    >
      Simple component
    </p>
  );
};

const CountComponent = () => {
  console.log("Render CountComponent");

  const [count, setCount] = useState(0);

  return (
    <div>
      <SimpleComponent />
      <p>Count = {count}</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        count up
      </button>
    </div>
  );
};

const TestPage = () => {
  console.log("Render TestPage");

  return (
    <div>
      <CountComponent />
    </div>
  );
};

export default TestPage;

 この例のようにStateの影響受けない場所に配置しても問題なければ移動させましょう。

 SimpleComponentをTestPageに移動させます。

 実行すれば分かるように、カウントアップしてもSimpleComponentは再描画されなくなります。

import { useState } from "react";

const SimpleComponent = () => {
  console.log("Render SimpleComponent");
  return (
    <p
      style={{
        width: "100%",
        backgroundColor: "black",
        color: "white",
      }}
    >
      Simple component
    </p>
  );
};

const CountComponent = () => {
  console.log("Render CountComponent");

  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count = {count}</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        count up
      </button>
    </div>
  );
};

const TestPage = () => {
  console.log("Render TestPage");

  return (
    <div>
      <SimpleComponent />
      <CountComponent />
    </div>
  );
};

export default TestPage;

純粋なコンポーネントは再描画させない


 SimpleComponentはpropsなど持たないため毎回同じ表示をします。

 先ほどの1つめの例では再描画されていましたが、そもそもSimpleComponent自体を再描画させないというやり方もあります。

 SimpleComponentを以下のようにReact.memoでラッピングすることで再描画しなくなります。

 この後詳細を記載しますがReact.memoの第二引数は2つのpropsを比較して再描画が不要かを判定する関数です。常にtrueにすることで再描画しなくなります。

import React from "react";
import { useState } from "react";

const _SimpleComponent = () => {
  console.log("Render SimpleComponent");
  return (
    <p
      style={{
        width: "100%",
        backgroundColor: "black",
        color: "white",
      }}
    >
      Simple component
    </p>
  );
};

const SimpleComponent = React.memo(_SimpleComponent, () => true);
...

メモ化React.memoで不要な描画を省略する


 前回の例に加えて桁数を表示するDigitComponentを作りCountComponentの中に入れます。

 DigitComponentは桁数をpropsで受け取ります。

 この場合はCountComponentでカウントアップしたらDigitComponentも再描画されます。

 しかし、DigitComponentは桁数が同じなら全く同じ描画をするので、桁が上がるまで再描画させない方がいいでしょう。

import React from "react";
import { useState } from "react";

const DigitComponent = ({ digit }) => {
  console.log("Render DigitComponent");
  return (
    <p
      style={{
        width: "100%",
        backgroundColor: "black",
        color: "white",
      }}
    >
      桁数は{digit}です。
    </p>
  );
};

const CountComponent = () => {
  console.log("Render CountComponent");

  const [count, setCount] = useState(0);

  const digit = count.toString().length;

  return (
    <div>
      <DigitComponent digit={digit} />
      <p>Count = {count}</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        count up
      </button>
    </div>
  );
};

 このような場合に先ほども出たReact.memoで余計な再描画をさせないようにします。

 DigitComponentを以下のように実装すると余計な描画が走らなくなります。

 React.memoの第二引数でpropsの比較処理を行います。digitが同じ値であれば再描画をさせないようにします。

const _DigitComponent = ({ digit }) => {
  console.log("Render DigitComponent");
  return (
    <p
      style={{
        width: "100%",
        backgroundColor: "black",
        color: "white",
      }}
    >
      桁数は{digit}です。
    </p>
  );
};

const DigitComponent = React.memo(
  _DigitComponent,
  (props1, props2) => props1.digit === props2.digit
);

 これを見て「なぜReact.memoがないとdigitが同じでも再描画されるのか?」と思うかもしれません。

 再描画は値の中身ではなくpropsの参照先が同じかどうかで判断していることが要因です。

 React.memoの第二引数をprops同士の===にすると中身は同じでも新規に作成されたpropsのため再描画が実行されてしまいます。

const DigitComponent = React.memo(
  _DigitComponent,
  (props1, props2) => props1 === props2
);

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です