useEffectのクリーンアップ関数はどこまで責任を持つべきか:破棄ロジックと構造設計のレビュー観点
ReactのuseEffect
には「クリーンアップ関数」という後始末の仕組みがあります。
この関数は、useEffect
が再実行されたり、コンポーネントがアンマウントされたときに呼ばれます。
この仕組み自体はシンプルですが、実装を誤るとイベントリスナーの解除漏れやタイマーのゾンビ化、外部接続の放置といった問題が生じ、構造が脆弱になります。
クリーンアップ関数とは何か
useEffect
が返す関数が「クリーンアップ関数」です。これは前回の副作用を無効化・破棄する目的で実行されます。
基本形
useEffect(() => {
const timer = setInterval(() => { ... }, 1000);
return () => {
clearInterval(timer); // ← クリーンアップ関数
};
}, []);
この構造を正しく理解していないと、副作用が積み重なり、意図しない挙動が発生します。
よくある破棄漏れ
NGパターン:リスナーの解除忘れ
useEffect(() => {
window.addEventListener('resize', onResize);
}, []);
Comment
@Reviewer: addEventListenerを行っているにもかかわらず、removeEventListenerでの解除処理がありません。これは不要なイベントが積み重なる温床になります。
改善例
useEffect(() => {
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
クリーンアップ関数の責務と分離設計
次のような構造は「やりすぎなクリーンアップ」としてレビュー指摘対象になり得ます。
責務過剰なクリーンアップ
useEffect(() => {
connectSocket();
startPolling();
return () => {
disconnectSocket(); // OK
stopPolling(); // グローバル制御?要分離検討
resetState(); // 状態リセット?Hookでやるべき
};
}, []);
すべてをここで行うと、useEffectの外部依存性が高くなり、Hook自体が密結合化します。
Hookへの責務移譲という設計判断
イベント破棄や解除処理をカスタムHook側へ移譲することで、コンポーネントの責務を軽量化できます。
責務分離の例
function useWindowResize(handler: () => void) {
useEffect(() => {
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, [handler]);
}
コンポーネント側は簡潔に
useWindowResize(() => {
setSize(window.innerWidth);
});
クリーンアップ設計のレビュー観点
図解:構造的責務の分離
まとめ
クリーンアップ関数は単なる後始末ではなく、構造上の責務分担を表すシグナルでもあります。
責務が集中していたらHook化を検討し、破棄忘れがないかレビューで丁寧に見ていくことが、長期的な安定性につながります。
Reactは副作用に寛容である一方、破棄の設計を怠ると静かにバグが育ちます。
レビューアーの視点で、構造として破棄が整理されているかを評価することが重要です。