💨

useEffectと周辺知識について

2024/09/27に公開

まずuseEffectとは

関数型コンポーネントで副作用を実行するためのフック

副作用とは、コンポーネントの描画以外に何か外部に影響を与える動作のこと

useEffect(() => {
  /* 第1引数*/
  console.log('副作用関数が実行されました!')
},[依存する変数の配列]) // 第2引数
  • 第1引数は、副作用を実行する関数。
  • 第2引数には、依存する変数を配列として指定することができ、この変数が変化したときに、副作用が再実行される。

第二引数の依存配列

第2引数を指定することで、第1引数で定義した関数の実行タイミングを制御することができる。定義方法としては以下の3つが挙げられる。

  • 指定無しの場合

依存配列を指定しない場合、コンポーネントが再レンダリングされるたびに副作用が実行される。レンダリングごとに何かしらの処理をしたい場合に使用される。

useEffect(() => {
 console.log('毎回実行される副作用');
});
  • 空の配列を指定

依存配列に空の配列 [] を指定すると、副作用は初回のレンダリング時にのみ実行される。副作用は一度だけ実行され、以後は再実行されない。

useEffect(() => {
console.log('初回のみ実行される副作用');
}, []);
  • 第2引数に指定されている場合

依存配列に特定の変数を指定すると、その変数が変化するたびに副作用が再実行される。これにより、変数の変更に応じた処理を行うことができる。

useEffect(() => {
console.log('countが変わった時に実行される副作用');
}, [count]);

依存性配列の変数の値が変わるたびに**useEffect**が再実行される場合に古い副作用が新しい副作用と干渉してしまうことあり。無限ループの温床になる可能性がある。

ライフサイクル

React コンポーネントのライフサイクルは、主に次の 3 つのフェーズに分かている。それぞれのフェーズでは、特定のイベントが発生し、これらのイベントに基づいて副作用を処理することが可能になる。

1. Mounting(マウント)

Mountingとは、コンポーネントが初めて画面に描画される段階。この時点でコンポーネントが最初に呼び出され、useEffectの処理が実行される。

処理流れとしては。

  • コンポーネントのインスタンス化
  • render 関数が呼ばれる
  • 初期描画が実行される
  • useEffect の副作用が実行される

2. Updating(アップデート)

Updatingとは、コンポーネントの状態 (state) や親コンポーネントから渡されるプロパティ (props) が変更され、再レンダリングが行われる段階。このフェーズでは、useEffectuseLayoutEffectに依存している変数が変更されると再実行される。

アップデートの原因となるイベント:

  • コンポーネント内のstateが変更される
  • 親コンポーネントからのpropsが変更される

処理流れとしては

  • コンポーネントのインスタンス化
  • render 関数が呼ばれる
  • 初期描画が実行される
  • クリーンアップ実行(依存配列による)
  • useEffect の副作用が実行される(依存配列による)

Mounting時と処理流れとしては、ほぼ同じだが違いとしては、クリーンアップ関数が実行されること。

3. Unmounting(アンマウント)

Unmountingとは、コンポーネントが不要になり、DOMから削除される段階。主な処理は、クリーンアップ。タイマーやイベントリスナーの解除、ネットワークリクエストのキャンセルなどが行われ、これを行わないとメモリリークが発生する可能性がある。

これらライフサイクルの処理を理解することで、useEffectの副作用や後述するクリーンアップ関数についての発火タイミングについて理解がしやすい。

クリーンアップ関数

クリーンアップ関数とはuseEffect内で定義される関数で、副作用の影響を取り除くために使用される。コンポーネントがアンマウントされる時や、依存配列の変数が変化して副作用が再実行されるタイミングで、処理などが残っていることによって、メモリリークや不要な動作が起こってしまうのを防ぐために実行させるもの。

なぜクリーンアップ関数が重要か?

  • メモリリーク防止: クリーンアップ関数を使わないと、イベントリスナーやタイマーが解除されずに残り続け、メモリリークの原因となることがある。これが特に長期間動作するアプリケーションでは問題になっていく。
  • パフォーマンスの最適化: 不要なリソースを適切にクリーンアップすることで、アプリケーションのパフォーマンスを向上させることができる。

実際の例

setIntervalsetTimeoutを使用したタイマーは、不要になった時点でクリアする必要がある。特にコンポーネントがアンマウントされた後もタイマーが動作し続けると、意図しない動作やメモリリークを引き起こすため、クリーンアップ関数でこれを処理行う。

useEffect(() => {
  const intervalId = setInterval(() => {
    console.log('毎秒実行される処理');
  }, 1000);

  // クリーンアップ関数でタイマーをクリア
  return () => {
    clearInterval(intervalId);
    console.log('クリーンアップ: インターバルがクリアされました');
  };
}, []);

他にもイベントリスナーの削除、APIリクエストのキャンセルなどが挙げられる。

クリーンアップ関数のタイミング

  • マウント時にはクリーンアップ関数は実行されない: 最初にuseEffectが実行される際には、クリーンアップ関数は実行されない。最初に実行されるのはuseEffect内の副作用関数のみ。
  • アップデート時のクリーンアップ: 依存配列に変数が指定されている場合、その変数が変わるたびに、まず前回の副作用のクリーンアップ関数が実行され、新しい副作用が再度実行される。
  • アンマウント時のクリーンアップ: コンポーネントがアンマウントされるとき、最後に登録されたクリーンアップ関数が実行され、リソースの解放が行われる。

useLayoutEffect

useLayoutEffectは、useEffectと似ているが、実行タイミングが異なり、useEffectはコンポーネント描画後に実行されるが、useLayoutEffectはコンポーネントの描画前に実行される

全体的な流れとしては

  1. React が仮想DOMを更新する
    1. (画面の差分を取得する。必要な部分だけを効率よく更新する仕組み)
  2. useLayoutEffect 内の副作用が実行される
  3. 画面に描画が行われる
  4. useEffect 内の副作用が実行される

使いどころ

useLayoutEffectは、DOMの寸法や位置を取得する必要があるときや、画面描画前にDOMを同期的に操作したい場合に使用される。

ただし、useLayoutEffectは描画をブロックしてしまうため、重い処理を行うとレンダリング自体が遅れる可能性がある。よって、処理が重くない場合や、描画タイミングに関わる操作が必要な場合に限定して使うべき。

Discussion