はじめに
こんにちは。アメリカ在住で独学エンジニアを目指している Taira です。
useState の初期値を設定するとき、次のように書いたことはありませんか?
const [count, setCount] = useState(expensiveComputation());
このように書くと、expensiveComputation() がレンダーのたびに実行されることがあります。
パフォーマンスの低下や、意図しない副作用の原因になることもあります。
この記事では、ReactのuseStateにおける「遅延初期化(lazy initialization)」について、
「なぜ() =>をつけると初回だけ実行されるのか?」を解説します。
🧬 useStateの仕様を理解する
useState は、次のようなシグネチャを持っています。
const [state, setState] = useState<T>(initialState: T | (() => T));
つまり、初期値には「値」か「関数」を渡せる仕様になっています。
React の内部では、このようなイメージで処理が行われます👇
const useState = (initialState) => {
if (isFirstRender) {
if (typeof initialState === "function") {
// 関数が渡された場合 → 初回マウント時に一度だけ実行して値を決定
state = initialState();
} else {
// 値が渡された場合 → そのまま初期値として使用
state = initialState;
}
}
return [state, setState];
};
この仕様により、
- 関数を渡すと → Reactが初回マウント時に一度だけ呼び出す
- 値を渡すと → そのまま使う(呼び出しはしない)
という動作の違いが生まれます。
💡 遅延初期化の書き方
// ❌ NG:毎回レンダーのたびに関数が呼ばれる
const [count, setCount] = useState(expensiveComputation());
// ✅ OK:Reactが初回のみ関数を呼ぶ
const [count, setCount] = useState(() => expensiveComputation());
違いは、関数をその場で実行しているか、Reactに任せているかです。
⚙️ 違いをコードで確認
const expensiveComputation = () => {
console.log("heavy function called");
return 42;
};
const App = () => {
console.log("render");
const [a] = useState(expensiveComputation());
const [b] = useState(() => expensiveComputation());
return (
<div>
<p>a: {a}</p>
<p>b: {b}</p>
</div>
);
};
🗒 コンソール出力(クリックで再レンダー)
render
heavy function called ← a(毎回実行)
heavy function called ← b(初回だけ)
-
useState(expensiveComputation())
→ JavaScriptがuseStateを呼ぶ前に関数を評価している -
useState(() => expensiveComputation())
→ Reactが初回マウント時のみ評価している
🤠 useMemoとの違い
「初回だけ実行」と聞くと、useMemoを思い浮かべる人も多いと思います。
しかし、両者の目的は異なります。
| フック | 実行タイミング | 目的 |
|---|---|---|
useState(() => …) |
初回レンダー時のみ | 初期値を計算してstateに保存 |
useMemo(() => …, []) |
初回レンダー時のみ | 計算結果をキャッシュ |
どちらも初回だけ実行されますが、
useState は「状態を保持」し、useMemoは「値をキャッシュ」するだけです。
🚀 まとめ
| ポイント | 内容 |
|---|---|
| 仕様 |
useStateは、引数が関数なら初回のみ実行、値ならそのまま評価 |
| 目的 | 初期値の計算を遅延させて無駄な再評価を防ぐ |
| 書き方 | useState(() => 初期化関数) |
| 効果 | パフォーマンス改善・意図しない副作用防止 |
| 補足 |
useMemoは値キャッシュ、useStateは状態管理 |
🎯 さいごに
「() =>をつけると初回だけ実行される」と聞くと魔法のように思えますが、
実際は React の useState が
「関数を渡されたら初回マウント時にだけ評価する」
という仕様になっているだけです。
つまり本質はこうです👇
useState(expensiveComputation())は「JavaScriptが呼ぶ」
useState(() => expensiveComputation())は「Reactが呼ぶ」
参考記事