ReactコンポーネントにおけるuseEffectは副作用処理の代表的な構造ですが、そのクリーンアップ関数は後回しにされがちです。レビュー現場では、「クリーンアップ処理が正しく記述されていない」「副作用が残留してメモリリークやイベント多重発火が発生する」といった指摘が頻繁に登場します。

本記事では、useEffectの中に定義されるクリーンアップ関数の設計責任をレビュー観点から見直し、正しい副作用の終息と構造的な責務分離の指針を明確にしていきます。

useEffectのクリーンアップ構造の基本

イベントリスナーの登録と解除
useEffect(() => {
  const handler = () => console.log("resized");
  window.addEventListener("resize", handler);
  return () => {
    window.removeEventListener("resize", handler);
  };
}, []);

このように、useEffect内で副作用の登録と解除を一対で記述するのがクリーンアップの基本構造です。

クリーンアップ関数とは

useEffectの戻り値として返す関数は、コンポーネントのアンマウント時や依存値の変化による再実行前に呼び出される「後始末処理」の関数です。

よくあるレビュー指摘:イベントやタイマーが残る構造

不完全な副作用の例
useEffect(() => {
  const intervalId = setInterval(() => {
    console.log("polling");
  }, 1000);
}, []);
コードレビュー
@Reviewer: `setInterval` のクリーンアップが存在していません。アンマウント後も動作し続け、メモリリークやバグの原因になります。`clearInterval` による解除処理を忘れずに。

このように、非同期系・永続系の副作用(イベント、タイマー、サブスクリプション)は必ず解除処理が必要です。

クリーンアップ責務の分離とカスタムHook化

責務が肥大化したuseEffect内部にすべての副作用とクリーンアップを詰め込むと、レビューもしづらくなります。

以下は責務を分離し、イベント登録の構造を明示したカスタムHookの例です。

useResizeHandler.ts
export function useResizeHandler(handler: () => void) {
  useEffect(() => {
    window.addEventListener("resize", handler);
    return () => {
      window.removeEventListener("resize", handler);
    };
  }, [handler]);
}
利用側
useResizeHandler(() => {
  console.log("resized");
});
コードレビュー
@Reviewer: クリーンアップ責務がHook内に封じ込められており、利用側は目的のみ記述することで誤用が防がれています。

複数副作用を1つのuseEffectにまとめすぎない

問題のある構造
useEffect(() => {
  const resizeHandler = () => console.log("resize");
  const scrollHandler = () => console.log("scroll");

  window.addEventListener("resize", resizeHandler);
  window.addEventListener("scroll", scrollHandler);

  return () => {
    window.removeEventListener("resize", resizeHandler);
    // scrollHandlerの解除を忘れている
  };
}, []);

副作用を一つのuseEffectに集約しすぎると、クリーンアップ漏れが起こりやすくなります。副作用は種類ごとに分け、個別にuseEffectを用いる構造の方が安全です。

可視化:副作用とクリーンアップの対応構造

UML Diagram

クリーンアップ関数のレビュー観点

まとめ

useEffect の副作用設計において、クリーンアップ関数は単なる付録ではなく、等しく設計責務を負う構造です。レビューアーとしては、「何が永続し、何を解除すべきか」を見極め、副作用がアプリの寿命を越えて残らないようにチェックを徹底する必要があります。

副作用は単体テストで検出しづらく、クリーンアップ漏れは後から深刻なバグになることもあるため、レビュー時の確認が唯一の防衛線になる場面も少なくありません。