【テクニカル・上級編】 useStateの遅延初期化(Lazy Initialization) – React実践ガイド

useStateの遅延初期化:その「重い初期値」がアプリの首を絞めている理由

やあ、フロントエンドの荒波で日々コードを磨き続けている同志よ。今日も元気にDOMのツリーを構築しているかい?

今回はReactの最も基本でありながら、うっかり見落としがちなパフォーマンスの魔窟、`useState`の「遅延初期化(Lazy Initialization)」について深く語らせてもらおう。

数々の大規模アプリケーションのコードレビューをしていると、初期値に直接重い関数を突っ込んでいるコードに遭遇する。開発者は「初期値だから最初の一回だけだろ」とたかをくくっているが、ブラウザのJavaScriptエンジン、そしてReactのレンダリングパイプラインの裏側を知る者からすると、それは冷や汗もののアンチパターンだ。

なぜそれがパフォーマンスのボトルネックになるのか。そして、Reactの内部で何が起きているのか。そのメカニズムと、エレガントな解決策を紐解いていこう。

—

1. 「初回だけだから重くてもいい」という幻想

まずは、多くのエンジニアが犯しがちな過ちを見てほしい。

import React, { useState } from ‘react’;

// 例えば、ローカルストレージから巨大なJSONをパースして、
// さらに何やら複雑な初期化処理を行う重い関数だとしよう
const loadAndParseHeavyData = () => {
console.info(‘【警告】重い初期化処理が走っています!’);
const rawData = localStorage.getItem(‘app-huge-state’);
// 実際にはここに重いパース処理や計算が入る
return rawData ? JSON.parse(rawData) : { items: [], version: 1 };
};

export const BadComponent: React.FC = () => {
// ❌ 痛恨のミス:毎回のレンダリングで loadAndParseHeavyData() が評価されている
const [state, setState] = useState(loadAndParseHeavyData());

return (

アイテム数: {state.items.length}

);
};

上記のコードで、ボタンをクリックして `setState` を呼び出し、コンポーネントが再レンダリングされたとき何が起きたか知っているかい?

コンソールには、ボタンを押すたびに 「【警告】重い初期化処理が走っています!」 というログが無慈悲に出力される。

「待て、`useState` の初期値なのだから、初回レンダリング時だけ実行されるはずだろ?」という声が聞こえてきそうだ。しかし、JavaScriptの言語仕様の基本に立ち返ってみてほしい。`useState(loadAndParseHeavyData())` と書いた時点で、関数呼び出し演算子 `()` は、`BadComponent` が評価(実行)されるたびに必ず実行される運命にある。

Reactは賢いので、2回目以降のレンダリング時には `useState` の引数(すでに計算された不要な戻り値)を単に無視する。だが、「関数を呼び出すこと自体」は毎回のレンダリングのたびにメインスレッドで実行されてしまっているのだ。これが、無駄なCPUサイクルを消費し、アプリケーションのフレームレートを静かに蝕む原因となる。

—

2. 遅延初期化(Lazy Initialization)のメカニズム

この問題をスマートに解決するのが、Reactが標準で用意してくれている遅延初期化のパターンだ。

やり方は極めてシンプル。`useState` に「実行結果」を渡すのではなく、「関数そのもの(参照)」を渡すだけだ。

import React, { useState } from ‘react’;

const loadAndParseHeavyData = () => {
console.info(‘【最適化】必要な初回のみ初期化処理が走ります’);
const rawData = localStorage.getItem(‘app-huge-state’);
return rawData ? JSON.parse(rawData) : { items: [], version: 1 };
};

export const GoodComponent: React.FC = () => {
// ⭕ 正解:関数自体を渡すことで、Reactはマウント時の初回のみこれを評価する
const [state, setState] = useState(() => loadAndParseHeavyData());

return (

アイテム数: {state.items.length}

);
};

これだけで、コンポーネントが何回再レンダリングされようとも、`loadAndParseHeavyData` は初回マウント時の一度しか実行されなくなる。

React内部では何が起きているのか?

Reactのファイバーアーキテクチャ(Fiber Architecture)において、Hooksの状態はリンクリストとして管理されている。
初回レンダリング時(Mount相)、`useState` に関数が渡された場合、Reactの内部ディスパッチャーはその関数を実行し、その戻り値を初期状態としてメモリーに保持する。

そして2回目以降の更新時(Update相)、Reactは単に内部のメモリーから現在のstateの値を取り出すだけであり、`useState` の引数に渡された関数がアロー関数(あるいは通常の関数)であれば、Reactはその評価を完全にスキップする。

この「初期値の評価を遅延させる(Lazy)」というアプローチは、パフォーマンスチューニングの基本中の基本でありながら、最も効果的な防衛策の一つなのだ。

—

3. 実務で遭遇する「本当に危ない」ユースケース

単なる `localStorage` のパースだけでなく、実務の現場では次のようなケースで遅延初期化が必須となる。

1. 複雑な初期設定値の生成(巨大な配列のシャッフルやダミーデータの生成)
2. DOMの計測(`getBoundingClientRect` などを伴う初期レイアウトの計算)
3. 外部ライブラリのインスタンス化(重いクライアントの初期化など)

特に、ウィンドウサイズやDOM要素のサイズに依存する初期状態を持つコンポーネントを考えてみよう。

import React, { useState } from ‘react’;

// DOMの計測を伴う重い初期化(SSR環境では window が存在しない点にも注意)
const getInitialDimensions = () => {
if (typeof window === ‘undefined’) return { width: 0, height: 0 };

console.info(‘ウィンドウサイズを計測中…’);
// 仮想的な重い処理やDOMアクセス
return {
width: window.innerWidth,
height: window.innerHeight,
};
};

export const WindowInspector: React.FC = () => {
// 遅延初期化を使わないと、不要な再レンダリングのたびにDOMやwindowへのアクセスが発生し、
// レイアウトスラッシングを引き起こすリスクが高まる
const [dimensions, setDimensions] = useState(() => getInitialDimensions());

return (

Width: {dimensions.width}, Height: {dimensions.height}

);
};

Next.jsなどのSSR(サーバーサイドレンダリング)フレームワークを使っている場合、`window` オブジェクトの存在チェックを安全に行うためにも、遅延初期化の関数内で環境をガードする書き方は非常に相性が良い。

—

4. アーキテクトからの提言:アンチパターンを避けるために

最後に、この遅延初期化を適用する際の注意点をいくつか共有しておこう。

  • 何でもかんでも関数で囲まないこと:

`useState(() => 0)` のように、プリミティブな値や単純なリテラルの生成に関数を渡すのは、逆にクロージャの生成コストが無駄になる。明確に「計算コストが高い」「I/Oを伴う」「大きなオブジェクト・配列の生成である」場合にのみ使おう。

  • 純粋関数(Pure Function)を保つこと:

初期化関数の中に副作用(外部の変数を書き換えるなど)を持たせてはならない。ReactのConcurrent Mode(並行レンダリング)において、初期化関数は複数回呼び出される可能性(または途中で中断される可能性)がある。初期化処理は必ず「冪等(べきとう)かつ純粋」であるべきだ。

コードの美しさは、細部へのこだわり宿る。たった一行の `() =>` を書き忘れたがために、目に見えないところでCPUが喘ぎ続ける――そんな悲劇を、今日この瞬間からあなたのプロジェクトで根絶してほしい。

プロとしての誇りを持って、高速で堅牢なコンポーネントを組み上げよう。 Happy Coding!

コメント

タイトルとURLをコピーしました