React Strict Modeの「二重実行」は、なぜ我々を救うのか?
Reactエンジニアなら一度は首を傾げたことがあるはずだ。「`useEffect`の中に書いた`console.log`が、なぜ開発環境では二回叩かれるのか?」と。
多くのジュニアエンジニアはこれを「Reactのバグ」や「フレームワークの気まぐれ」と片付けてしまう。しかし、フロントエンドの深淵を覗く我々にとって、これはReactが提供する最も強力な「予防医学」に他ならない。
今日は、Strict Modeにおける副作用の二重実行が、なぜあなたのアプリケーションを堅牢にするのか、そしてそれを正しく制御するためのアーキテクチャについて、技術的深層から紐解いていこう。
—
1. なぜ「二回」なのか? その背景にある哲学
React 18から導入されたStrict Modeにおける`useEffect`の二重実行は、単なるデバッグ用のノイズではない。これは「副作用の冪等性(Idempotency)」を強制的にテストするための、フレームワークによるシミュレーションだ。
Reactの理念において、コンポーネントは本来「純粋であるべき」だ。しかし、API通信やDOM操作といった副作用は不可避である。ここで重要になるのが「マウント→アンマウント→再マウント」というサイクルを、コンポーネントが正しく処理できるかどうかという点だ。
二重実行を行うことで、Reactは開発者にこう問いかけている。
「君の書いた副作用は、もしReactが将来的にコンポーネントを一時的に破棄して再利用するような挙動(Fast Refreshや将来的なConcurrent Features)をとったときでも、壊れないほど堅牢か?」
2. 冪等性を破壊するアンチパターン
多くのバグは、「一度だけ実行される」という前提に依存することで生まれる。典型的な例を見てみよう。
useEffect(() => {
// 良くあるアンチパターン:APIを叩いて外部の状態を更新する
// これが二回走ると、リクエストが重複し、競合が発生する可能性がある
api.subscribe(handleEvent);
// クリーンアップ関数が空、あるいは不足している
// これにより、コンポーネントが再マウントされるたびにイベントリスナーが蓄積し、メモリリークの温床になる
}, []);
このコードは、Strict Modeでは「二回購読」される。もし`api.subscribe`が内部でIDを発行するような仕様なら、二回目はエラーになるか、ゴミデータが蓄積する。これを防ぐのがクリーンアップ関数という名の「契約」だ。
3. 実践:二重実行を乗りこなすクリーンアップ戦略
堅牢なアプリケーションを作るための鉄則は、「副作用をセットアップした瞬間に、それを打ち消す手段を用意すること」だ。
useEffect(() => {
let active = true; // 副作用の有効性を管理するフラグ
const fetchData = async () => {
const data = await api.getData();
if (active) {
// コンポーネントが破棄されていたら実行しない
setData(data);
}
};
fetchData();
// 必須:クリーンアップ関数
return () => {
active = false; // 次の実行、あるいはアンマウント時に副作用を無効化
api.cleanup(); // 外部リソースの解放
};
}, [dependency]);
このコードでは、二度目の実行が発生しても、一度目のクリーンアップが確実に走る。これにより、リクエストの競合を防ぎ、メモリ効率を維持できる。これが「プロの仕事」だ。
4. なぜこれがパフォーマンス最適化に直結するのか
「二回実行されるなら、パフォーマンスが悪いのでは?」と考えるかもしれない。しかし、逆だ。
Strict Modeで露呈する二重実行の問題を放置することは、ブラウザのメモリリークを放置することと同義だ。例えば、React外のライブラリ(D3.jsやThree.jsなど)を`useEffect`内で初期化している場合、クリーンアップでインスタンスを破棄しないと、コンポーネントの再レンダリングのたびにブラウザのメモリを食いつぶし、最終的にタブがクラッシュする。
「副作用を二回叩かれても、結果が常に同じ(あるいは無害である)」という状態(冪等性)を確保することは、現代の複雑なフロントエンドにおいて、安定性を担保する唯一の道なのだ。
結びに代えて
React Strict Modeの二重実行は、あなたを困らせるためにあるのではない。「君のコードは、不確定な未来のレンダリングサイクルに耐えられるか?」という、Reactからの挑戦状だ。
この挑戦を「面倒くさい」と捉えるか、「設計を研ぎ澄ますチャンス」と捉えるか。それだけで、エンジニアとしての格が一段上がる。
コードを書くとき、常に自問してほしい。
「もしこの`useEffect`が、今この瞬間に二回実行されたら、何が起きるだろうか?」
その問いの先にこそ、真に堅牢なアーキテクチャが待っている。

コメント