こんにちは!フロントエンドの現場を渡り歩いているチーフアーキテクトの私です。
Reactを学び始めて、いざコードを書き始めたとき、こんな現象に出くわしたことはありませんか?
「あれ? `console.log` を1回しか書いていないのに、画面を開いたらいきなりコンソールに2回ログが出るんだけど……我的なコード、何か壊しちゃった!?」
……大丈夫です、安心してください。あなたのコードは壊れていませんし、あなたが何か変な呪文を唱えたわけでもありません。
今回は、Reactの初心者さんが100%通る「あるあるの登竜門」、「Strict Mode(ストリクトモード)における二重レンダリングと `useState` の関係」について、難しい専門用語はなるべく置いておいて、身近な例えを交えながら優しく紐解いていきますね。
—
1. なぜ「2回」動くの? Reactからのちょっと大きなお世話
まずは、この怪奇現象の正体からお話ししましょう。犯人は 「Strict Mode(厳格モード)」 という、Reactが用意してくれている開発用の“お節介なコーチ”です。
想像してみてください。あなたが新入社員として会社に入ったとき、ベテランの先輩が後ろからそっとあなたの作業を覗き込んで、
「あ、それだと後でミスしやすいから、今のうちに予行練習で2回やっておこうか!」
と、あえて二度手間をさせられたことはありませんか? Strict Modeはまさにこれです。
ReactのStrict Modeは、私たちが開発している最中だけ、「コンポーネントの関数や、状態の初期化処理をあえて『2回』実行する」という特訓を裏でこっそり行っています。
なぜそんなことをするの?
「いやいや、2回も動かされたら重いしバグるじゃん!」って思いますよね。でも、これにはちゃんとした理由があります。
Reactの世界では、画面の表示やデータの管理が「未来の予測不可能なタイミング」で行われることがよくあります。そのとき、もしあなたの書いたコードが「1回しか実行されないこと」を前提に雑に作られていると、アプリが複雑になったときに突然おかしな動き(バグ)をするんです。
だから、Reactはあえて開発中に2回実行してみて、
「おいおい、2回動かしてもちゃんと機嫌よく動く頑丈なコードになってるかい?」
と、私たちのコードをテストしてくれているわけですね。本番環境(ユーザーが実際に使う環境)ではこの2回実行は消えるので、安心してください。
—
2. `useState` の初期化と二重レンダリングの罠
ここで本題の `useState` のお話です。データを画面に保持するために欠かせない `useState` ですが、Strict Modeの二重実行によって、初学者が思わず「あれ?」と頭を抱えるポイントがあります。
例えば、以下のようなコードを書いてみたとしましょう。
import { useState } from ‘react’;
export default function Counter() {
// 初期値を決めるための関数
const [count, setCount] = useState(() => {
console.log(“初期化関数が実行されました!”);
return 0;
});
return (
現在のカウント: {count}
);
}
これをStrict Modeが有効なアプリで画面に表示すると、ブラウザのコンソールにはこう表示されます。
初期化関数が実行されました!
初期化関数が実行されました!
「ひえっ、2回出た!」
ここで大切なポイントをお伝えします。「初期化関数が2回呼ばれること自体は、Reactの仕様上、全く問題ありません」。
Reactは、1回目の実行結果をそっと捨てて、2回目の結果を正式な初期値として採用します。だから画面上の表示がおかしくなることはありません。
本当に怖いのは「副作用(Side Effect)」がある初期化
ここで一つ、初心者の頃にやりがちな「危ない書き方」を見てみましょう。
// ❌ やっちゃいけないアンチパターン例
let globalCounter = 0;
export default function DangerComponent() {
const [count, setCount] = useState(() => {
globalCounter += 1; // 外部の変数を書き換えちゃっている!
console.log(`初期化回数: ${globalCounter}`);
return 0;
});
return
;
}
このコードをStrict Modeで動かすと、コンソールにはこう出ます。
初期化回数: 1
初期化回数: 2
もし、この「外部の変数を書き換える処理」が、例えば「サーバーへのデータの送信」や「データベースの書き込み」だったらどうでしょう? 画面を開いただけで、意図せず2回もデータが送信されてしまいますよね。
これが、Reactが私たちに「関数の中身は純粋にしておきなさい(外の世界を勝手に汚しちゃダメだよ)」と教えてくれている理由です。`useState` の初期化やコンポーネントの中身は、「何度(何回)実行されても、結果が絶対に変わらない(安全な)」書き方をする、これがReactマスターへの第一歩です。
—
3. 実務で役立つ! 安全な `useState` のお作法
では、私たちはどうやってこのStrict Modeとうまく付き合っていけばいいのでしょうか? 実務の現場でも使える、ちょっとしたコツをいくつかご紹介します。
① 初期化の計算が重いときは「関数」で渡す
お買い物のレジを想像してください。毎回計算し直すと面倒な重い計算は、一番最初にレジを開けたとき(初期化時)だけ計算したいですよね。
そんなときは、`useState(0)` のように直接数値を書くのではなく、アロー関数(遅延初期化)を使います。
// 👍 GOODな書き方
const [userList, setUserList] = useState(() => {
// ここで重いlocalStorageの読み込みなどをやってもOK
console.log(“重い初期化処理が走りました(Strict Modeでは2回動きます)”);
return [“りんご”, “みかん”, “バナナ”];
});
こう書くことで、Reactは「あ、最初の1回目(正確にはテストの2回も含めて最初だけ)に安全に呼んでね」と理解してくれます。
② 「2回動く」を前提にデバッグする癖をつける
コンソールログを見るときに、「あ、今はStrict Modeだから2回出るんだな」と心構えをしておくだけで、無駄な不安やパニックを防ぐことができます。
もしどうしても「1回だけの動作を確認したい!」という場合は、一時的に `main.js` や `App.jsx` あたりにある `
—
まとめ:Reactからの「優しさ」だと受け取ろう
さて、ここまでStrict Modeと `useState` の関係についてお話ししてきましたが、いかがでしたでしょうか?
- Strict Modeは、コンポーネントをあえて2回動かして、私たちのコードの「頑丈さ」をテストしてくれているお節介で優しいコーチ。
- `useState` の初期化が2回呼ばれるのは仕様の範囲内なので焦らなくて大丈夫。
- ただし、初期化やコンポーネントの内部で「外部のデータを書き換える(副作用)」ような危ないことをしないように気をつける。
最初は「なんだこれバグか!?」とドキッとする挙動ですが、Reactが私たちのアプリを将来の大きなバグから守ってくれている防波堤だと思えば、少し見方も変わってくるはずです。
もしまた開発中に変な挙動に出くわしても、深呼吸をして「大丈夫、Reactがテストしてくれているんだな」と優しく受け止めてあげてくださいね。あなたのReactライフを、チーフアーキテクトとしてこれからも応援しています!

コメント