こんにちは!フロントエンドの現場を渡り歩いてきた、ちょっとおせっかいなチーフアーキテクトです。
Reactを触り始めの頃って、`useEffect`という魔法の箱(フック)に翻弄されがちですよね。「なんか画面を開いた瞬間に無限ループしてブラウザがフリーズした…」「あれ、値が変わったのに再描画されない…」なんて冷や汗をかいた夜を、あなたも過ごしていませんか?
大丈夫です。その焦り、みんな最初は通る道ですから安心してください。
今回は、そんな`useEffect`のなかでも、ついやりがちな「副作用のなかで、`if`文を使って実行するかどうかを迷うパターン」について、おしゃべりするようにじっくり解説していきたいと思います。
—
1. 例え話:おうちの「自動センサーライト」で考えてみよう
まずは、身近なものでイメージを膨らませてみましょう。
廊下に設置する「自動センサーライト」を思い浮かべてください。このライトは、人間が前を通ると「ピカッ」と光ります。
Reactの`useEffect`は、まさにこのセンサーライトのようなものです。
- 依存配列(`[ ]`の中身):センサーが「何を監視するか」の設定。例えば「人の動き」や「周囲の明るさ」。
- 副作用関数の中身:センサーが反応したときに「実際にライトを点ける・消す」というアクション。
ここで、今日の本題です。
「センサー自体は人が通るたびに反応する(=`useEffect`は発火する)けれど、ライトのスイッチのところで『あ、今は昼間だから点灯しなくていいや』と`if`文で弾く」
というアプローチについて考えてみます。
これって、一見すると賢いやり方に見えるのですが、実は現場のエンジニアたちの間では「ちょっと待った!」と議論になるポイントなんです。
—
2. 現場でよく見る「useEffectの中のif文」ってどんなもの?
まずは、初学者の頃に誰もが一度は書いてしまうようなコードを見てみましょう。
例えば、「ユーザーがログインしている時だけ、特別なデータをサーバーから読み込みたい」というシーンを想像してください。
import React, { useState, useEffect } from ‘react’;
function UserDashboard() {
const [user, setUser] = useState(null);
const [data, setData] = useState(null);
// ユーザー情報が変わるたびにuseEffectが呼ばれる
useEffect(() => {
// 【ここに注目】useEffectの”中”でif文を書いちゃった!
if (user === null) {
console.log(‘まだログインしてないから、何もしません!’);
return; // 処理を中断
}
console.log(‘ログインしてるね!データを取ってくるよ!’);
fetchDataForUser(user.id);
}, [user]); // 依存配列には user を入れている
// … 略
}
このコード、動かしてみるとちゃんと動きます。エラーも起きません。
「なんだ、これでいいじゃん!」って思いますよね?
でも、チーフアーキテクトの視点から少しだけ耳打ちさせてください。この書き方、「センサーは常にガチャンガチャンと反応しているのに、肝心のライトのところで『あ、やっぱりやめた』って毎回ブレーキを踏んでいる状態」なんです。
—
3. なぜ「useEffectの中でのif文」が問題になりやすいの?
「動くならいいじゃない」と思いますよね。なぜこれがアンチパターン(避けたほうがいい書き方)と言われることがあるのでしょうか。理由は主に2つあります。
① 「えっ、なんでここで動かないの?」というバグの温床になる
`useEffect`の依存配列に指定された値が変わると、Reactは「お、この箱の中身を実行しなきゃ!」と律儀に走ります。
しかし、関数の中の深いところで`if`文によりガードされていると、「コード上はフックが動いているのに、実質的な処理がスルーされる」という現象が起きます。
これが複雑なコンポーネントになってくると、「あれ、このデータなんでフェッチされないんだっけ?」と追うのがもの凄く大変になるんです。
② 無駄な「空回り」コストがかかる
センサー(Reactのライフサイクル)自体は毎回の変更のたびに起動しています。中身の処理は`if`で弾いているとはいえ、関数が呼び出されるオーバーヘッドはゼロではありません。
—
4. じゃあ、どう書くのがプロっぽい?(正解のアプローチ)
では、この「条件付きで何かを実行したい」という場合はどうすればいいのでしょうか?
答えはシンプルで、「useEffectを呼ぶかどうかを、そもそも外側(呼び出しのタイミング)でコントロールする」か、あるいは「依存配列の力を信じる」ことです。
先ほどのコードを、もう少しすっきりと整理してみましょう。
パターンA:useEffectを呼ぶ条件そのものを外側でガードする(王道)
そもそも、ユーザーがいない(`user === null`)なら、データ取得の副作用自体を走らせたくないわけです。それなら、`useEffect`の外側(コンポーネントの本体)で早期リターン(Early Return)するか、条件をスッキリさせましょう。
import React, { useState, useEffect } from ‘react’;
function UserDashboard() {
const [user, setUser] = useState(null);
const [data, setData] = useState(null);
// ユーザーがいないなら、そもそもこの下にあるデータ取得用のuseEffectにたどり着かせない!
useEffect(() => {
// ここに到達した時点で、userは「絶対に存在する」と確信できる
console.log(‘確実にログイン済み!データを取ってくるよ!’);
fetchDataForUser(user.id);
}, [user?.id]); // ユーザーIDが変わったときだけ純粋に反応する
// …
}
どうでしょう? `useEffect` の中に「もし〜なら何もしない」という迷いの `if` 文が消えて、すごく見通しが良くなりませんでしたか?
「このフックが動くということは、もう前提条件はクリアしているんだな」と、コードを読む人が一目で理解できるようになります。
—
5. それでも「中のif文」が許される例外的なケース
ここまで「中に`if`文を書くのは避けよう」とお伝えしてきましたが、実は実務の現場では、あえて中に`if`文を書くのが正解になるケースも少しだけあります。
それは、「ブラウザのイベントや、リアルタイムのタイマー、細かいフラグの変化など、Reactのライフサイクル外の条件をその場でチェックしたい時」です。
例えば、「ウィンドウの幅が特定のサイズ以上の時だけ、アニメーションを動かしたい」といったケースです。
useEffect(() => {
const handleResize = () => {
// 画面幅が1024px以上のときだけ処理をする(その場の動的な条件チェック)
if (window.innerWidth >= 1024) {
console.log(‘デスクトップ表示用の特別な処理をします’);
}
};
window.addEventListener(‘resize’, handleResize);
// クリーンアップ関数を忘れないでね!
return () => {
window.removeEventListener(‘resize’, handleResize);
};
}, []);
このように、イベントリスナーのコールバックや、リアルタイムで変化する値をその場でジャッジする目的であれば、`if`文は大活躍します。
—
6. まとめ:今日の学びを持ち帰ろう!
- `useEffect`の中での `if` 文は、安易に使うと「コードの迷子」を生み出しやすい。
- 基本的には、副作用を走らせたい「大前提の条件」は、フックの外側や依存配列、あるいはコンポーネントの手前でスッキリ整理してあげよう。
- 「このフックは何のためにあるんだっけ?」と未来の自分やチームメンバーが迷わない、透明度の高いコードを目指そう!
Reactのフックは、最初はちょっと気難しく感じる相棒のようなものです。でも、コツさえ掴んでしまえば、あなたの強力な味方になってくれます。
もし今日書いたコードで躓いても、「あ、チーフが言ってたのはこういうことかな」と思い出してもらえたら嬉しいです。
それでは、また次回の現場の知見でお会いしましょう!Happy Hacking!

コメント