こんにちは、フロントエンドチームの皆さん。
日々の機能開発やリファクタリング、本当にお疲れ様です。
さて、今日はReactの開発現場で「おや?」と誰もが一度はハマる、そして中級からシニアへステップアップする上で絶対に避けて通れないテーマについて話をしようと思う。
そう、「React Strict ModeとPropsのライフサイクル」だ。
開発環境でアプリを立ち上げたとき、コンソールログがなぜか2回出力されて「あれ、俺のコード、何かバグらせたか…?」と冷や汗をかいた経験はないだろうか?
特に、親から子へ渡すPropsの挙動や、副作用(`useEffect`)のタイミングで頭を悩ませたはずだ。
今日は、この「Strict Mode下での二重レンダリング」が裏側で何を引き起こしているのか、そして我々フロントエンドエンジニアがどう立ち回るべきなのかを、実務の視点から徹底的に紐解いていこう。
—
1. なぜReact Strict Modeは「2回レンダリング」するのか?
まず大前提として共有しておきたい。本番環境(Production)では、この二重レンダリングは発生しない。 これは開発環境(Development)だけの特権、いや、Reactチームからの「愛のムチ」だ。
一体なぜ、Reactはわざわざ同じコンポーネントを2回レンダリングし、Propsを2回流し込むようなお節介な真似をするのだろうか?
その理由は極めてシンプルかつ強烈だ。それは、「あなたのコンポーネントが『純関数(Pure Function)』であるかをあぶり出すため」だ。
リアクティブな世界における「純粋性」の担保
Reactのレンダリングロジック(JSXを返すまでのプロセス)は、理論上「同じPropsとStateが入力されれば、常に全く同じUI(出力)を返さなければならない」という原則(純粋性)で成り立っている。
もし、コンポーネントのレンダリング中に以下のような「副作用(Side Effects)」をやらかしているとどうなるだろう?
- グローバル変数を書き換えている
- タイムスタンプやランダムな値をその場で生成してDOMに反映している
- 予期せぬ外部の状態を直接ミューテート(破壊的変更)している
これらがあると、Reactが非同期でレンダリングを中断・再開したり、Strict Modeで2回レンダリングを走らせた瞬間に、UIがぶっ壊れるか、予測不可能なバグ(不整合)を生むことになる。
ブラウザの裏側では、V8エンジンなどのJavaScriptエンジンがメモリ上に仮想DOMを構築し、差分検出(Reconciliation)を行っている。Strict Modeはこのプロセスをあえて2回連続で実行することで、「このコンポーネントは何度呼び出されても安全か?」「Propsの変更に対して完全に予測通りに振る舞うか?」を厳しくテストしているのだ。
—
2. Propsの受け渡しとライフサイクルへの影響
では、このStrict Modeが「親から子へ渡すProps」にどのような影響を与えるのか、具体的なユースケースで考えてみよう。
中級エンジニアによくある勘違いが、「Propsが変わったから2回レンダリングされたんだ」という誤認だ。そうではない。Props自体は同じであっても、コンポーネントのライフサイクル(マウント時)の検査として、Reactは意図的に「マウント ➔ アンマウント ➔ 再マウント」に近いシミュレーションを開発環境で行う。
これに伴い、以下のような現象が起きる。
1. 親から渡された初期Propsを元に、子が1回目のレンダリングと副作用を実行。
2. Strict Modeの検閲により、即座にクリーンアップが走り、再度同じPropsで2回目のレンダリングと副作用を実行。
もし、この「副作用(`useEffect`内でのデータフェッチや外部APIの叩きなど)」の設計が甘いと、開発環境だけでAPIが2回叩かれるといった無駄なリソース消費やバグに直面することになる。
—
3. 【実務向け】安全なPropsの設計とコードパターン
百聞は一見に如かず。実際に、Strict Modeの二重レンダリングや厳格なPropsの型付けに耐えうる、モダンで堅牢なコンポーネントの実装を見ていこう。
今回は、親から渡されたユーザーID(Props)を元にデータを処理し、安全に副作用をハンドリングする例だ。TypeScriptの恩恵を最大限に受けつつ、実務でそのままコピペして使える品質に仕上げている。
import React, { useState, useEffect } from ‘react’;
// 1. Propsの型定義は明確かつ厳格に行う(interfaceを使用するのが実務の標準)
interface UserProfileProps {
userId: string;
onDataLoaded?: (userName: string) => void;
}
export const UserProfile: React.FC
const [userName, setUserName] = useState
const [isLoading, setIsLoading] = useState
useEffect(() => {
// 2. クリーンアップ関数用のフラグを用意
// Strict Modeの二重実行による競合を防ぐための実務テクニック
let isMounted = true;
const controller = new AbortController();
const fetchUserData = async () => {
try {
setIsLoading(true);
// ダミーのAPIリクエスト(実際にはfetchなどが入る)
// AbortControllerを渡すことで、アンマウント時の不要な通信をキャンセル
const response = await fetch(`https://api.example.com/users/${userId}`, {
signal: controller.signal,
});
if (!response.ok) throw new Error(‘データ取得に失敗しました’);
const data = await response.json();
// コンポーネントがマウントされている場合のみ状態を更新
if (isMounted) {
setUserName(data.name);
// 親から渡されたコールバックがあれば実行
onDataLoaded?.(data.name);
}
} catch (error: any) {
if (error.name !== ‘AbortError’) {
console.error(‘APIエラー:’, error);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
fetchUserData();
// 3. クリーンアップの徹底
// Strict Mode下での2回目のマウントの前に必ずここが呼ばれる
return () => {
isMounted = false;
controller.abort();
};
}, [userId]); // userIdが変わった時のみ副作用を走らせる
if (isLoading) {
return
;
}
return (
ユーザープロフィール
名前: {userName ?? ‘名無しさん’}
ID: {userId}
);
};
このコードの優れたポイント(シニアの解説)
1. `isMounted` フラグと `AbortController` の合わせ技
Strict Modeによって `useEffect` が短時間に2回走った際、1回目の非同期処理が完了する前に2回目の処理が始まると、古いレスポンスが新しい状態を上書きしてしまう「競合状態(Race Condition)」が起きる。これを防ぐために、クリーンアップでフラグを倒し、通信自体もキャンセルしている。これが実務で「バグらないUI」を作る鉄則だ。
2. Propsの型付けとオプショナルコールバック
`onDataLoaded?: (userName: string) => void` のように、親へデータを伝播させるPropsも厳格に型付けしている。これにより、親コンポーネント側でのタイポや型ミスマッチをコンパイル時に完全に排除できる。
—
4. チーフアーキテクトからの実践的なアドバイス
Strict Modeによる二重レンダリングやライフサイクルの挙動に直面したとき、初心者は「なぜか2回動くから `useRef` でフラグを立ててガードしよう」といった小手先のハック(アンチパターン)に走りりがちだ。
しかし、それをやってしまうとReactの哲学(宣言的UIと純粋性)から外れ、将来的により複雑なバグの温床になる。
- 「レンダリング(JSXの生成)」は何度実行されても副作用ゼロで安全であるべき。
- 「副作用(API通信やDOM操作など)」は `useEffect` に閉じ込め、クリーンアップ機構を必ず実装する。
この2点を守るだけで、React Strict Modeはあなたを困らせる「お邪魔虫」から、最高に頼もしい「バグ発見器」へと変わるはずだ。
現場のコードレビューで「あ、こいつ分かってるな」と思わせるような、堅牢で美しいコンポーネント設計をこれからも心がけていってほしい。質問があればいつでもチームのチャンネルに投げ銭ならぬ質問を投げてくれ。
それでは、今日のコーディングも楽しんでいこう!

コメント