Reactアプリで意図しない再レンダリングを防ぐ具体的対策と実例

Reactアプリケーションの開発において、パフォーマンスの最適化は重要な課題の一つです。特に、意図しない再レンダリングは、アプリの動作を遅くし、ユーザーエクスペリエンスを損なう要因となることがあります。Reactの仮想DOMや状態管理の仕組みは便利ですが、不適切な実装によって不要な再レンダリングが発生することも少なくありません。本記事では、再レンダリングの仕組みを理解し、その発生を防ぐための具体的な対策やツールの活用方法について解説します。これにより、より効率的で応答性の高いReactアプリを構築するための知識を深めることができます。

目次

再レンダリングの仕組みと意図しない原因


Reactは仮想DOMを使用してUIを効率的に更新する仕組みを持っています。状態やプロパティが変更されると、Reactはその変更をトリガーに再レンダリングを実行し、仮想DOMと実際のDOMの差分を計算して更新を最適化します。しかし、この便利な仕組みも、特定の条件下では不要な再レンダリングを引き起こす場合があります。

再レンダリングの基本的な仕組み


再レンダリングは、以下のようなプロセスで進行します:

  1. 状態またはプロパティが変更される。
  2. Reactは関連するコンポーネントの再レンダリングをスケジュールする。
  3. 再レンダリングされた仮想DOMを以前の仮想DOMと比較する。
  4. 差分に基づいて、実際のDOMを更新する。

この過程は通常効率的に動作しますが、変更が不要な部分にまで波及すると、余計な計算や処理が発生します。

意図しない再レンダリングが発生する原因


意図しない再レンダリングの主な原因は次の通りです:

  1. プロパティの変更による影響
    親コンポーネントが再レンダリングされると、子コンポーネントにも再レンダリングが伝播します。これはたとえ子コンポーネントのプロパティに変更がなくても発生します。
  2. オブジェクトや配列の再生成
    プロパティとして渡されるオブジェクトや配列が、状態更新時に新たに生成されると、Reactはそれを新しい値とみなし、再レンダリングを実行します。
  3. コールバック関数の再生成
    useStateやuseEffect内で関数が再生成されると、それを使用しているコンポーネントが再レンダリングされる可能性があります。
  4. 依存関係の誤設定
    useEffectやuseMemoなどのフックで依存配列を適切に設定しないと、不要な処理が再実行されて再レンダリングが引き起こされます。

これらの原因を把握することで、再レンダリングの適切な制御が可能になります。次章では、意図しない再レンダリングがパフォーマンスに与える影響について詳しく説明します。

再レンダリングによるパフォーマンスの影響

意図しない再レンダリングが発生すると、Reactアプリケーションのパフォーマンスに大きな影響を与えることがあります。これにより、ユーザー体験が低下し、特に大規模なアプリケーションや複雑なUIコンポーネントでは顕著なパフォーマンス低下が見られます。

パフォーマンス低下の具体例

  1. 遅延の発生
    再レンダリングが頻繁に発生すると、ブラウザの描画プロセスが負荷を受け、UIの反応が遅れることがあります。例えば、ボタンをクリックした際に明らかな遅延が発生することがあります。
  2. 高CPU使用率
    不要な計算や仮想DOMの差分比較が増加することで、CPU使用率が急上昇し、他のプロセスに悪影響を及ぼす可能性があります。
  3. ユーザーの体感遅延
    アニメーションやインタラクションがスムーズに動作せず、視覚的なカクつきが発生することで、アプリケーションの品質が低下します。

再レンダリングの波及効果


意図しない再レンダリングは、以下のようにアプリ全体に影響を及ぼします:

  • 親子コンポーネント間の連鎖
    親コンポーネントの再レンダリングが子コンポーネントに波及することで、変更が不要な部分まで処理が実行されます。
  • 大量のDOM操作
    仮想DOMと実際のDOM間の差分計算が無駄に増え、DOM操作が多発します。これにより、レンダリングエンジンの負荷が高まります。

実際のケーススタディ


例えば、以下のようなコードを考えます:

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <ChildComponent name="Static Name" />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function ChildComponent({ name }) {
  console.log("ChildComponent rendered");
  return <p>{name}</p>;
}

この場合、ParentComponentの状態が更新されるたびに、変更がないChildComponentも再レンダリングされます。console.logが意図しない回数だけ実行され、性能に影響を与えます。

適切な対策を行うべき理由


意図しない再レンダリングを抑制することで、以下のメリットが得られます:

  • ユーザーインターフェースがスムーズに動作する。
  • アプリ全体の処理効率が向上する。
  • モバイル端末などリソースが限られた環境でも安定した動作を維持できる。

次章では、必要なレンダリングと不要なレンダリングを見極める方法について詳しく解説します。

必要なレンダリングと不要なレンダリングの違い

Reactアプリケーションのパフォーマンスを最適化するためには、必要なレンダリングと不要なレンダリングを区別することが重要です。必要なレンダリングはアプリの正しい動作のために不可欠なプロセスですが、不必要なレンダリングはパフォーマンスの低下を引き起こします。この章では、それらの違いと見極め方について解説します。

必要なレンダリングとは


必要なレンダリングは、以下の条件を満たす場合に発生します:

  1. ユーザーインタラクションによる変更
    ボタンのクリックやフォーム入力など、ユーザーアクションによって状態やプロパティが変更された場合。
    例: ボタンをクリックしてカウンターの値を増加させる。
   const [count, setCount] = useState(0);
   return <button onClick={() => setCount(count + 1)}>{count}</button>;
  1. 表示内容が変更される必要がある場合
    状態やプロパティが変化し、それに応じてUIの表示内容を更新する必要がある場合。
  2. 外部データの変更による更新
    APIから取得したデータや外部イベントによる状態の変更がある場合。
    例: WebSocketやAPIのレスポンスでデータを更新する。

不要なレンダリングとは


不要なレンダリングは、次のような状況で発生します:

  1. 状態やプロパティに実際の変更がない場合
    コンポーネントの状態やプロパティが変更されていないにもかかわらず、再レンダリングが実行されるケース。
  2. 親コンポーネントの影響
    親コンポーネントが再レンダリングされることで、変更のない子コンポーネントまで再レンダリングされる。
    例: 親コンポーネントの状態が変わったことで子コンポーネントが再レンダリングされるが、子のUIに変更はない。
  3. オブジェクトや配列の参照が変わる場合
    同じ値を持つオブジェクトや配列が再生成され、新しい参照として扱われることで再レンダリングが発生する。
   const items = [1, 2, 3];
   return <ChildComponent items={items} />;

上記のコードは、itemsが同じ値でも新しい参照として渡されるたびにChildComponentが再レンダリングされます。

不要なレンダリングの検出方法


React開発ツールやconsole.logを使用してレンダリングの挙動を確認し、必要か不要かを判断します:

  1. React開発ツールのProfiler
    Profilerタブを使用して、どのコンポーネントが再レンダリングされているかを視覚的に確認できます。
  2. ログ出力の利用
    コンポーネントのレンダリング時にconsole.logでログを出力し、不要な再レンダリングが発生していないか確認します。

必要なレンダリングと不要なレンダリングを区別する意義

  • 必要なレンダリングだけに集中することで、アプリケーションのパフォーマンスが向上する。
  • コードの意図が明確になり、メンテナンス性が高まる。

次章では、具体的にメモ化を活用して不要なレンダリングを防ぐ方法について詳しく解説します。

メモ化を活用したレンダリング制御

Reactで不要な再レンダリングを防ぐための効果的な方法として、メモ化が挙げられます。Reactは、コンポーネントや関数の結果をキャッシュすることで、状態やプロパティの変化に応じた必要な部分だけを再レンダリングする仕組みを提供しています。この章では、React.memouseMemouseCallbackを活用した具体的な手法を解説します。

React.memoでコンポーネントをメモ化する


React.memoを使用すると、プロパティが変更されない限りコンポーネントを再レンダリングしないように設定できます。

const ChildComponent = React.memo(({ name }) => {
  console.log("ChildComponent rendered");
  return <p>{name}</p>;
});

function ParentComponent() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <ChildComponent name="Static Name" />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

上記の例では、React.memoにより、ChildComponentはプロパティnameに変更がない限り再レンダリングされません。これにより、無駄なレンダリングを防ぐことができます。

カスタム比較関数を利用する


プロパティの比較方法をカスタマイズしたい場合、React.memoに比較関数を渡せます:

const ChildComponent = React.memo(
  ({ items }) => {
    console.log("ChildComponent rendered");
    return <p>{items.join(", ")}</p>;
  },
  (prevProps, nextProps) => {
    return prevProps.items.length === nextProps.items.length;
  }
);

この例では、itemsの長さが同じ場合、再レンダリングをスキップするように設定しています。

useMemoで計算結果をキャッシュする


useMemoは、計算コストの高い関数の結果をキャッシュし、依存する値が変わらない限り再計算を防ぎます。

function ExpensiveCalculation({ count }) {
  const result = React.useMemo(() => {
    console.log("Calculating...");
    return count * 2;
  }, [count]);

  return <p>Result: {result}</p>;
}

この例では、countが変わるたびに計算を実行し、それ以外の場合は以前の結果を再利用します。

useCallbackで関数をメモ化する


useCallbackは、関数の生成を制御して、プロパティとして渡された関数が再生成されることを防ぎます。

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  const increment = React.useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return <ChildComponent onClick={increment} />;
}

const ChildComponent = React.memo(({ onClick }) => {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>Click me</button>;
});

このコードでは、increment関数が再生成されないため、ChildComponentの再レンダリングを回避できます。

メモ化の適切な使い方


メモ化は強力ですが、適切な場面で使用することが重要です:

  1. 再レンダリングが頻繁に発生する部分に適用する。
  2. 計算コストが高い関数にuseMemoを使用する。
  3. 親子間のプロパティに渡す関数をuseCallbackでメモ化する。

メモ化による効果

  • 不必要なレンダリングが抑えられることで、パフォーマンスが向上する。
  • 開発者が意図した通りの挙動を保証できる。

次章では、状態管理を最適化して再レンダリングを防ぐ方法を解説します。

状態管理の最適化

Reactでの状態管理は、コンポーネントのレンダリングに直接影響を与える重要な要素です。不適切な状態管理は、意図しない再レンダリングの原因となります。この章では、状態管理の仕組みを理解し、効率的に状態を管理する方法を解説します。

useStateの適切な使用


useStateはシンプルな状態管理に便利ですが、状態の設計によっては再レンダリングを招きやすくなります。以下はその対策の一例です。

状態を細かく分割する


1つの状態に複数の値をまとめるのではなく、関連性のない値は分けて管理することで不要なレンダリングを防ぎます。

function Component() {
  const [count, setCount] = React.useState(0);
  const [text, setText] = React.useState("");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

このように分割することで、counttextが独立して更新され、それぞれが原因で他方の再レンダリングを引き起こさないようになります。

同じ参照を維持する


状態が変化していない場合、Reactは新しいレンダリングを必要としません。同じ参照を維持することが重要です。

const [items, setItems] = React.useState([]);
const addItem = React.useCallback(() => {
  setItems((prev) => [...prev, "New Item"]);
}, []);

useCallbackを使用することで、関数が毎回新しく生成されるのを防ぎます。

useReducerを活用する


複雑な状態管理が必要な場合は、useReducerを使用するとコードの管理がしやすくなり、不要な再レンダリングを抑えることができます。

const reducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "updateText":
      return { ...state, text: action.payload };
    default:
      return state;
  }
};

function Component() {
  const [state, dispatch] = React.useReducer(reducer, { count: 0, text: "" });

  return (
    <div>
      <button onClick={() => dispatch({ type: "increment" })}>
        Increment: {state.count}
      </button>
      <input
        value={state.text}
        onChange={(e) => dispatch({ type: "updateText", payload: e.target.value })}
      />
    </div>
  );
}

useReducerは複数の状態をまとめて管理でき、状態の変更に応じたレンダリングを最適化します。

状態のリフトアップを適切に行う


複数のコンポーネント間で共有する状態は、親コンポーネントにリフトアップすることで管理します。ただし、状態のリフトアップは過剰に行うと親コンポーネントが頻繁に再レンダリングされるため、必要最小限に留めます。

状態管理ライブラリの活用


アプリケーションが大規模化する場合、ReduxRecoilZustandなどの状態管理ライブラリを使用すると、効率的に状態を管理できます。これらのライブラリはグローバル状態のスコープを限定し、特定のコンポーネントだけを再レンダリングする仕組みを提供します。

状態管理の最適化による効果

  • 再レンダリングが最小限に抑えられ、アプリのパフォーマンスが向上する。
  • コードがシンプルになり、バグが減少する。
  • 状態変更の影響範囲をコントロールしやすくなる。

次章では、コンポーネント分割と依存関係管理による再レンダリング抑制のテクニックを解説します。

コンポーネント分割と依存関係管理

Reactアプリケーションで意図しない再レンダリングを防ぐためには、適切なコンポーネント分割と依存関係の明確化が重要です。これにより、変更が必要な部分だけを再レンダリングし、他の部分への波及を防ぐことができます。

適切なコンポーネント分割の重要性


コンポーネントを適切に分割することで、以下のメリットがあります:

  1. 再レンダリングの範囲を限定できる
    子コンポーネントが独立している場合、親の状態変更による波及を防げます。
  2. コードの可読性と再利用性が向上する
    小さな責任を持つコンポーネントを設計することで、コードの理解やメンテナンスが容易になります。

分割の例


次のコードは、再レンダリングを制御するためにコンポーネントを分割した例です:

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <ChildComponent />
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

const ChildComponent = React.memo(() => {
  console.log("ChildComponent rendered");
  return <p>I'm a child component!</p>;
});

このコードでは、ChildComponentReact.memoでメモ化されているため、ParentComponentcountが変わっても再レンダリングされません。

依存関係の明確化


依存関係が不明確な場合、意図しない再レンダリングが発生することがあります。以下に、依存関係を明確化する方法を示します。

useEffectでの依存配列の設定


useEffectを使用する際、依存配列を適切に設定することで不要な再実行を防げます。

React.useEffect(() => {
  console.log("Effect executed");
}, []); // 依存配列を空にすることで、初回のみ実行される

依存配列に必要な値を明確に記述することで、依存関係をコントロールできます。

状態の適切なスコープ管理


状態を必要なスコープ内に限定することで、影響範囲を最小化できます。

function ParentComponent() {
  const [localState, setLocalState] = React.useState(0);

  return (
    <div>
      <button onClick={() => setLocalState(localState + 1)}>
        Update Local State
      </button>
      <ChildComponent />
    </div>
  );
}

この例では、localStateが親コンポーネントに限定され、子コンポーネントは再レンダリングの影響を受けません。

依存関係管理のポイント

  1. プロパティや状態のスコープを明確にする
    子コンポーネントに必要なプロパティだけを渡すことで、影響範囲を限定します。
  2. 不要な再レンダリングをデバッグする
    React開発ツールのProfilerを使用して、どの依存関係が再レンダリングを引き起こしているかを特定します。

コンポーネント分割と依存関係管理の効果

  • 必要な部分だけを効率的に更新できる。
  • アプリケーションのパフォーマンスが向上する。
  • コードの設計が明確になり、可読性が高まる。

次章では、デバッグツールを用いた再レンダリングの検出方法を解説します。

デバッグツールによる再レンダリングの検出

Reactアプリケーションで意図しない再レンダリングが発生している場合、デバッグツールを活用することで原因を効率的に特定できます。この章では、React Developer Toolsやログを使用して再レンダリングの挙動を検出する方法を解説します。

React Developer Toolsを活用する

React Developer Tools(React DevTools)は、Reactアプリケーションのコンポーネントツリーや状態、再レンダリングのプロファイリングを行うための強力なツールです。

Profilerを使用したレンダリングの検出


React DevToolsのProfilerタブを使用すると、どのコンポーネントが再レンダリングされたかを視覚的に確認できます。

  1. Profilerの有効化
    Reactアプリケーションをブラウザで開き、React DevToolsのProfilerタブを選択します。
  2. レンダリングの記録
    「Start profiling」をクリックしてアプリの操作を記録します。操作を終了したら「Stop profiling」をクリックします。
  3. 結果の分析
    記録したデータから、どのコンポーネントが再レンダリングされたか、どの程度の時間がかかったかを確認できます。再レンダリングが不要と思われるコンポーネントが記録されている場合、その原因を特定します。

Highlighted Updatesでレンダリングを視覚化


React DevToolsの設定で「Highlight updates when components render」を有効にすると、再レンダリングが発生したコンポーネントがブラウザ上でハイライト表示されます。これにより、どの部分が影響を受けているかが直感的に把握できます。

ログ出力によるデバッグ

ログを使用して再レンダリングのタイミングを確認することも有効です。

コンポーネント内でのログ出力


コンポーネントのレンダリング時にconsole.logを挿入することで、どのタイミングで再レンダリングが発生したかを記録します。

function ChildComponent() {
  console.log("ChildComponent rendered");
  return <p>Child Component</p>;
}

これにより、意図しない再レンダリングが発生している箇所を検出できます。

PropsやStateの変更を監視


ログを使ってプロパティや状態の変化を記録し、再レンダリングのトリガーを特定します。

function ParentComponent({ propValue }) {
  React.useEffect(() => {
    console.log("Prop changed:", propValue);
  }, [propValue]);
}

再レンダリング検出のベストプラクティス

  • Profilerでボトルネックを確認する
    再レンダリングに時間がかかっているコンポーネントを特定し、最適化を行います。
  • ログとデバッグツールを併用する
    開発中にログで細かい挙動を確認し、全体の挙動はProfilerで視覚的に把握します。
  • ハイライト機能で問題箇所を素早く特定する
    視覚的なフィードバックにより、問題の箇所を特定しやすくなります。

デバッグツール活用の効果

  • 意図しない再レンダリングを迅速に検出できる。
  • 問題のある箇所を特定し、対策を講じやすくなる。
  • アプリの挙動を視覚的に確認することで、修正ポイントを明確化できる。

次章では、意図しない再レンダリング問題の具体的な解決例について解説します。

再レンダリング問題の具体的な解決例

Reactアプリケーションで意図しない再レンダリングが発生した場合、原因を特定したうえで適切な対策を実施することが重要です。この章では、再レンダリング問題を解決した具体的な例を紹介し、それぞれの対策がどのように効果を発揮するかを解説します。

例1: 親コンポーネントの状態変更による波及を防ぐ


問題: 親コンポーネントの状態更新により、子コンポーネントが意図せず再レンダリングされている。

解決策: 子コンポーネントをReact.memoでメモ化する。

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <ChildComponent name="Static Name" />
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

const ChildComponent = React.memo(({ name }) => {
  console.log("ChildComponent rendered");
  return <p>{name}</p>;
});

結果:
React.memoにより、ChildComponentnameプロパティに変更がない限り再レンダリングされなくなり、パフォーマンスが向上します。

例2: 配列やオブジェクトの再生成による再レンダリングの抑制


問題: 親コンポーネントで配列やオブジェクトが毎回新たに生成されることで、子コンポーネントが再レンダリングされる。

解決策: useMemoを使用して配列やオブジェクトをメモ化する。

function ParentComponent() {
  const [count, setCount] = React.useState(0);
  const items = React.useMemo(() => [1, 2, 3], []);

  return (
    <div>
      <ChildComponent items={items} />
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

const ChildComponent = React.memo(({ items }) => {
  console.log("ChildComponent rendered");
  return <p>{items.join(", ")}</p>;
});

結果:
useMemoにより、itemsが再生成されなくなり、ChildComponentが意図せず再レンダリングされる問題が解決します。

例3: コールバック関数の再生成による再レンダリングの抑制


問題: 親コンポーネントで関数が毎回新たに生成されることで、子コンポーネントが再レンダリングされる。

解決策: useCallbackを使用して関数をメモ化する。

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  const handleClick = React.useCallback(() => {
    console.log("Button clicked");
  }, []);

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

const ChildComponent = React.memo(({ onClick }) => {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>Click me</button>;
});

結果:
useCallbackにより、handleClickが再生成されず、ChildComponentが不要に再レンダリングされる問題が解消されます。

例4: グローバル状態管理の最適化


問題: ReduxやContext APIを使用してグローバル状態を管理している場合、特定の状態が更新されるたびにすべてのコンポーネントが再レンダリングされる。

解決策: 状態を必要なコンポーネントに限定する。

function ParentComponent() {
  const [globalState, setGlobalState] = React.useState({ count: 0, text: "" });

  return (
    <div>
      <CountDisplay count={globalState.count} />
      <TextDisplay text={globalState.text} />
      <button onClick={() => setGlobalState({ ...globalState, count: globalState.count + 1 })}>
        Increment
      </button>
    </div>
  );
}

const CountDisplay = React.memo(({ count }) => {
  console.log("CountDisplay rendered");
  return <p>Count: {count}</p>;
});

const TextDisplay = React.memo(({ text }) => {
  console.log("TextDisplay rendered");
  return <p>Text: {text}</p>;
});

結果:
状態を分割して渡すことで、CountDisplayTextDisplayの再レンダリングを独立させ、必要な部分だけが再レンダリングされるようになります。

具体例から得られる教訓

  • 不必要なレンダリングを検知した後、適切なメモ化や状態分割を行うことで、パフォーマンスが劇的に向上します。
  • 再レンダリング問題を解決するには、Reactの内部挙動を理解し、ツールを効果的に活用することが重要です。

次章では、再レンダリング防止の技術を強化するための演習問題を紹介します。

演習問題で理解を深める

Reactアプリケーションにおける再レンダリング防止の技術を実践的に学ぶため、以下の演習問題を試してみてください。それぞれの問題では、再レンダリングを最小限に抑えるための知識を活用できます。

演習問題1: `React.memo`の活用


問題:
以下のコードでは、ParentComponentの状態が更新されるたびに、ChildComponentが再レンダリングされています。React.memoを使用して、ChildComponentが不要に再レンダリングされないように修正してください。

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <ChildComponent />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function ChildComponent() {
  console.log("ChildComponent rendered");
  return <p>I'm a child component!</p>;
}

ヒント:

  • React.memoを適切に適用する。

演習問題2: `useMemo`での最適化


問題:
次のコードでは、ExpensiveCalculation関数が毎回実行されるため、アプリのパフォーマンスが低下しています。useMemoを使用して、この関数を最適化してください。

function App() {
  const [count, setCount] = React.useState(0);

  const result = ExpensiveCalculation(count);

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function ExpensiveCalculation(num) {
  console.log("Calculating...");
  return num * 2;
}

ヒント:

  • useMemoを利用してExpensiveCalculationをメモ化する。

演習問題3: 子コンポーネントへの関数のメモ化


問題:
以下のコードでは、ParentComponenthandleClick関数が毎回再生成されるため、ChildComponentが再レンダリングされています。useCallbackを使用してこの問題を解決してください。

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    console.log("Button clicked");
  };

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

const ChildComponent = React.memo(({ onClick }) => {
  console.log("ChildComponent rendered");
  return <button onClick={onClick}>Click me</button>;
});

ヒント:

  • useCallbackを使用してhandleClickをメモ化する。

演習問題4: 状態の分割


問題:
以下のコードでは、globalStateを一括管理しているため、どちらかの状態が変更されるたびに、両方の子コンポーネントが再レンダリングされています。状態を分割して、必要なコンポーネントだけが再レンダリングされるように修正してください。

function ParentComponent() {
  const [globalState, setGlobalState] = React.useState({ count: 0, text: "" });

  return (
    <div>
      <CountDisplay count={globalState.count} />
      <TextDisplay text={globalState.text} />
      <button onClick={() => setGlobalState({ ...globalState, count: globalState.count + 1 })}>
        Increment
      </button>
      <input
        value={globalState.text}
        onChange={(e) => setGlobalState({ ...globalState, text: e.target.value })}
      />
    </div>
  );
}

const CountDisplay = ({ count }) => {
  console.log("CountDisplay rendered");
  return <p>Count: {count}</p>;
};

const TextDisplay = ({ text }) => {
  console.log("TextDisplay rendered");
  return <p>Text: {text}</p>;
};

ヒント:

  • 状態をuseStateで分割し、それぞれ独立して管理する。

問題解決後の確認


各問題に対して修正を加えた後、console.logやReact DevToolsのProfilerを使用して、再レンダリングの挙動が改善されていることを確認してください。

次章では、再レンダリングの防止技術や本記事で学んだ知識をまとめます。

まとめ

本記事では、Reactアプリケーションにおける意図しない再レンダリングを防ぐためのさまざまな方法を解説しました。再レンダリングの仕組みを理解し、具体的な原因を特定することで、適切な対策を講じることができます。以下に、重要なポイントを振り返ります:

  1. 再レンダリングの仕組みを理解する
    Reactの仮想DOMと依存関係の仕組みを把握することで、再レンダリングの原因を明確にできます。
  2. メモ化を活用する
    React.memouseMemouseCallbackを使用して、不要な再レンダリングを効果的に抑制します。
  3. 状態管理を最適化する
    状態の分割やスコープの限定を行い、必要な部分だけを更新する設計を心がけます。
  4. デバッグツールで検証する
    React Developer ToolsのProfilerやconsole.logを活用して、再レンダリングの問題箇所を特定し、修正後の動作を確認します。
  5. 実践的な演習を行う
    問題を実際に解決する経験を通じて、再レンダリングの防止技術を習得します。

意図しない再レンダリングを防ぐことは、アプリケーションのパフォーマンス向上だけでなく、ユーザー体験の向上にも直結します。これらのテクニックを活用して、効率的でスムーズなReactアプリケーションを構築してください。

コメント

コメントする

目次