useEffectは本当に必要か?副作用設計と責務分離のレビュー指針
React開発において useEffect
は頻出のフックであり、コンポーネント内部の副作用処理を記述する場として重宝されています。しかし、レビューアーの視点でコードを読むと「そもそもこのuseEffectは本当に必要か?」「この副作用、どこまで責任を持っているのか?」という判断に迷うケースが多く見られます。
本記事では、useEffect
の構造的な設計責任を見直し、レビューアーとして不要な記述・不明瞭な責務・潜在的バグの温床になり得る構造をどう見抜き、どう指摘するかを体系的に解説します。
技術背景:useEffectが導入された理由とその本質
Reactがクラスコンポーネントから関数コンポーネントに進化した中で、componentDidMount
や componentDidUpdate
、componentWillUnmount
に相当する処理を関数内で管理するために useEffect
が導入されました。
そのため、本来 useEffect
は「副作用」を明示的に書く場所であり、「状態変更」や「データ取得」、「イベントリスナー登録」などのコンポーネント外部とのインタラクションを記述することを目的としています。
にもかかわらず、実際の現場では「処理のタイミング制御」「初期化のついで」「依存の都合で」など、構造的に曖昧な意図で使われていることが少なくありません。
よくある誤用例とその構造的問題
以下は典型的な useEffect
誤用の例です。
const MyComponent = ({ count }: { count: number }) => {
const [internal, setInternal] = useState(0);
useEffect(() => {
setInternal(count);
}, [count]);
return <div>{internal}</div>;
};
@Reviewer: このuseEffectは `count` をそのまま表示すれば済む構造であり、副作用の導入は不要です。`useEffect + useState` の構成は明示的な理由がない限り避けるべきです。
useEffectによる状態更新は、「依存するpropsを内部状態にコピーする」ような構造になっていると、更新の意図・データの責務が不明瞭になります。レビュー時には「内部状態が本当に必要か?」「その変更タイミングは正確か?」を確認してください。
副作用責務の明確化チェックリスト
以下はレビュー時に useEffect
の正当性を判断するための視点です。
これらのいずれにも該当しない場合、その useEffect
は構造的に不要である可能性が高いです。
状態更新を含むuseEffect構造の見直し方
以下のような構造は、useEffectを介さずに実現可能な場合があります。
const MyComponent = ({ defaultValue }: { defaultValue: string }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(defaultValue);
}, [defaultValue]);
return <input value={value} />;
};
@Reviewer: このような構造は `useState(defaultValue)` に置き換えることで、初期化処理を明示的に記述できます。副作用を導入する合理的理由が見当たりません。
副作用の粒度が曖昧な構造に対するレビュー
1つの useEffect
の中で複数の異なる処理が混在していると、責務が不明瞭になります。レビューアーとしては処理単位の粒度と依存関係を再構築する提案を行う必要があります。
useEffect(() => {
fetchUser().then(setUser); // API副作用
window.addEventListener("resize", handleResize); // DOMイベント副作用
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
@Reviewer: 複数の責務(データ取得、イベントリスナー登録)が混在しています。責務単位で `useEffect` を分離するか、必要であればカスタムHookへの切り出しを検討してください。
よくある設計ミス:依存配列と無関係な処理
useEffect
の依存配列に含まれない値を参照していると、意図しない再実行や不安定な動作の原因となります。
useEffect(() => {
if (user.role === "admin") {
fetchAdminConfig();
}
}, []);
@Reviewer: `user.role` を使用しているにもかかわらず、依存配列に含まれていないため、将来的にバグを誘発します。明示的に依存を記述してください。
カスタムHookへの抽出判断と責務の分離
useEffectが複雑化している場合、処理をカスタムHookに切り出すことで構造的責務を明示できます。
function useAdminConfig(user: User) {
useEffect(() => {
if (user.role === "admin") {
fetchAdminConfig();
}
}, [user.role]);
}
const Dashboard = ({ user }: { user: User }) => {
useAdminConfig(user);
return <div>ようこそ、{user.name}さん</div>;
};
このように構造を切り出すことで、責務の明示性と再利用性が高まります。レビューアーとしては「この副作用は別コンポーネントでも使えるか?」「構造的に抽出可能か?」を観点にすると、抽象化の適用判断がしやすくなります。
責務と構造の可視化(PlantUML)
まとめ:レビューアーとして何を指摘すべきか
useEffect
を導入する構造的理由があるか?- 処理単位の責務が混在していないか?
- 依存配列が副作用ロジックと合致しているか?
- 状態更新に
useEffect
を使う必要があるか?初期化だけで完結できないか? - 抽出可能な副作用をコンポーネントに埋め込んでいないか?
これらの観点で構造的な責任範囲を明確にし、可読性・再利用性・意図の伝達性を高める設計を提案できるレビューアーであることが求められます。