こんにちは。プロダクトの規模が膨らみ、`useEffect` の嵐に頭を悩ませているあなたなら、一度はこう思ったことがあるはずです。
「依存配列で細かく制御するの、もう限界じゃないか? いっそ副作用の中で `if` 文で弾いちゃえば楽なんじゃ……?」
よくぞその疑問に行き着いてくれました。実はこれ、中級から一皮むけて「真のReactアーキテクト」へステップアップする過程で、誰もが一度は通る「禁断の扉」なんですよね。
今日は、現場のリアルな泥臭い知見を交えながら、この 「`useEffect` 内部での条件分岐」 について徹底的に解剖していきましょう。結論から言うと、「安易な `if` は地獄への片道切符だが、ブラウザのライフサイクルとReactの挙動を完全に手なずければ、最強の武器になる」 です。さあ、一緒に紐解いていきましょうか。
—
なぜ私たちは「副作用の中での条件分岐」に惹かれてしまうのか?
現場でコードレビューをしていると、こんな `useEffect` によく出会います。
// 良くある、副作用の中でフラグをチェックしちゃうパターン
useEffect(() => {
if (!isReady || !userId) {
return; // 準備できてないから何もしない!
}
// ここから本番の重い処理
fetchUserData(userId);
}, [isReady, userId]);
「お、ちゃんと `if` でガードしてて安全じゃん」と思いました?
実はこれ、Reactの仕組みを少し知っている人間からすると、「依存配列と副作用のロジックがねじれを起こしている典型的なシグナル」 なんです。
ブラウザとReactの裏側の動きを覗いてみよう
まず、Reactがどのように動いているかを思い出してください。
`useEffect` は、「レンダー(レンダリングフェーズ)」が完了し、ブラウザが画面の描画を終えた後(コミットフェーズの後)に非同期で実行されます。
つまり、上のコードで何が起きているかというと:
1. `isReady` が `false` から `true` に変わる。
2. コンポーネントが再レンダリングされる。
3. ブラウザが画面をペイントする。
4. Reactが「よし、`useEffect` の出番だな」と副作用関数を起動する。
5. 副作用の中で `if (!isReady)` に引っかかり、「あ、やっぱやーめた」と何もせずに帰っていく。
……お気づきですか? 画面の描画が終わった後に、無駄な関数呼び出しと条件判定のコストをわざわざ払っているんです。さらに、依存配列 `[isReady, userId]` にはこれらがしっかり入っているため、Reactは「あ、値が変わったから副作用をスケジュールし直さなきゃ」と律儀に毎度タスクを積んでいます。
依存配列で制御するのか、関数内の `if` で弾くのか。この役割分担が曖昧になると、コードのメンテンス性は一気に地の底へと落ちていきます。
—
現場で使える!「依存配列」と「if文」の正しい使い分けの境界線
じゃあ、副作用の中の `if` はすべて悪なのか? そんなことはありません。実務では、以下の明確な基準で使い分けるのがプロの技です。
1. 依存配列で制御すべきもの(Reactのライフサイクル・同期のトリガー)
- 「どの値が変わったら、この副作用を“最初からやり直すべき(再実行・クリーンアップすべき)”か」という依存関係の定義。
2. 副作用内部の `if` で制御すべきもの(早期リターン・実行時の一時的なガード)
- 依存している値はあるけれど、「現在のコンテキストやDOMの状態、一時的なフラグによって、今回は処理をスキップしたい」というランタイムのガード。
特に、DOM要素の存在確認(Refがアタッチされているかなど)や、イベントリスナー内の条件分岐などは、副作用内部で `if` を使うのが正解ケースが多いです。
—
実践!美しく堅牢なコードで学ぶパターン
百聞は一見に如かず。ここでは、実務でそのまま使える、洗練されたパターンをコードで見ていきましょう。
テーマは 「カスタムフックと組み合わせた、安全なデータフェッチとイベント監視」 です。
import React, { useState, useEffect, useRef } from ‘react’;
type UserProfileProps = {
userId: string | null;
isActive: boolean;
};
export const UserProfileCard: React.FC
const [userData, setUserData] = useState
const [isLoading, setIsLoading] = useState
// DOM参照や、レンダリングをトリガーしたくない一時的な状態管理には useRef が鉄則
const isMountedRef = useRef
// コンポーネントのアンマウントを検知するためのクリーンアップ
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
// 【パターンA】依存配列による「同期のトリガー」の制御
// userId が変わった時だけ、データフェッチのライフサイクルを再起動する
useEffect(() => {
// 早期リターン(if文によるガード)の活用:
// userId が存在しない場合は、そもそもフェッチ処理を行わない。
// これにより、無駄な非同期処理や通信の発生を防ぐ。
if (!userId) {
setUserData(null);
return;
}
let isCancelled = false;
const controller = new AbortController();
const fetchProfile = async () => {
setIsLoading(true);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const data = await response.json();
// もし非同期処理の途中でコンポーネントがアンマウントされていたら、
// もしくは別のリクエストに置き換わっていたら状態を更新しない(メモリリーク防止)
if (!isCancelled && isMountedRef.current) {
setUserData(data);
}
} catch (error: any) {
if (error.name !== ‘AbortError’) {
console.error(‘データの取得に失敗しました:’, error);
}
} finally {
if (!isCancelled && isMountedRef.current) {
setIsLoading(false);
}
}
};
fetchProfile();
// クリーンアップ関数:古いリクエストのキャンセルとフラグの書き換え
return () => {
isCancelled = true;
controller.abort();
};
}, [userId]); // ここには userId のみを依存させる。isActive はここではトリガーにしない。
// 【パターンB】副作用内部の if による「ランタイム実行制御」
// isActive の変更はフェッチ自体のトリガーにはしたくないが、
// 「アクティブな時だけ特定のイベントやロジックを動かしたい」という要件のケース
useEffect(() => {
// 依存配列に isActive を入れない代わりに、ここで現在の状態をチェックする
// あるいは、isActive が変わった時に即座に何かをハンドリングしたいなら依存配列に入れるべき。
// 今回は「非アクティブ時は処理を無視する」というガードとしての if 文。
if (!isActive) {
return;
}
const handleWindowFocus = () => {
console.log(‘ウィンドウがフォーカスされました(アクティブ時のみ実行)’);
// ここにアクティブ時限定のポーリング再開処理などを書く
};
window.addEventListener(‘focus’, handleWindowFocus);
return () => {
window.removeEventListener(‘focus’, handleWindowFocus);
};
}, [isActive]); // isActive の変化に応じてリスナーの登録を張り替える場合は依存配列に入れるのがReact的作法
return (
読み込み中…
}
{userData ? (
{userData.name}
{userData.email}
) : (
!isLoading &&
ユーザーが選択されていません。
)}
);
};
このコードの何が優れているのか?(シニアからの解説)
1. 「いつ副作用を走らせるべきか」が依存配列で一目でわかる
`userId` が変わったときだけデータを再取得する、というライフサイクルが依存配列を見るだけで完璧に把握できます。
2. 副作用内部の `if (!userId)` による安全な早期リターン
不要なタイミングでのロジック実行を防ぎつつ、コードのネストを深くせずにすっきりと保っています。
3. `AbortController` と `isMountedRef` の合わせ技
`if` で弾くだけでなく、非同期処理の途中で状況が変わった場合の「後始末(クリーンアップ)」まで完璧にケアされています。これぞプロのプロダクトクオリティです。
—
シニアアーキテクトからのメッセージ
副作用の内部で `if` 文を使うこと自体は、決してタブーではありません。むしろ、不必要な再レンダリングやバグを防ぐための「賢い盾」として機能します。
ただし、「依存配列で管理すべきライフサイクルのコントロール」を `if` 文で無理やり隠蔽しようとするのは絶対にやめましょう。 それは技術的負債という名の爆弾をコードベースに埋め込んでいるのと一緒です。
Reactとブラウザの対話を意識し、「何がトリガーで、何がガードなのか」を明確に分離する。これができるようになると、あなたの書くコードの美しさと堅牢性は見違えるように変わります。
さあ、明日からのコードレビューで、チームメンバーをうならせてやりましょう!何か疑問があれば、いつでもチャットで聞いてください。

コメント