Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】🚀 React useState Lazy Initialization: A Complete Guide

2025年09月30日に「Dev.to」が公開したITニュース「🚀 React useState Lazy Initialization: A Complete Guide」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Reactの`useState`で重い初期値計算を行う場合、再レンダリングごとに無駄に実行され、パフォーマンスが低下する。遅延初期化を使うと、初期値設定の関数を渡すことで初回レンダリング時のみ実行される。これにより`localStorage`からの読み込みや複雑な計算など、コストのかかる処理を効率化し、アプリケーションの動作を速くできる。

ITニュース解説

Reactアプリケーションを開発する上で、コンポーネントが持つデータを管理するためにuseStateフックは非常に重要な機能を提供する。これは、コンポーネント内で変化する可能性のあるデータを記憶し、そのデータに変更があった際にはコンポーネントを自動的に更新する役割を担う。多くの場面で、このuseStateには直接初期値を指定して使用する。例えば、カウンターの初期値を0に設定するなら、const [count, setCount] = useState(0)のように書くのが一般的だ。この方法は分かりやすく、ほとんどのケースで十分に機能する。

しかし、初期値を設定する際の計算が「重い」場合や、その計算結果が初回レンダリング時に一度だけ必要であるにもかかわらず、何度も実行されてしまうような特定の状況では、この直接的な初期値の割り当て方法が最も効率的ではないことがある。ここで言う「重い計算」とは、例えばWebブラウザのlocalStoragesessionStorageからデータを読み込む操作、あるいは非常に複雑な計算や大量のデータを解析する処理など、時間やコンピューターのリソースを多く消費する可能性がある操作を指す。

具体的に、const [count, setCount] = useState(expensiveCalculation());のように、初期値の箇所に「重い計算を行う関数expensiveCalculation()の呼び出し」を直接記述した場合を考えてみよう。このexpensiveCalculation()は、コンポーネントが最初に画面に表示されるとき(初回レンダリング時)には確かに実行され、その結果がcountの初期値となる。しかし、Reactのコンポーネントは、ユーザーの操作や他のデータの変更によって、一度表示された後も何度も再レンダリングされることがある。このとき、expensiveCalculation()は、初回以外は初期値として結果が必要ないにもかかわらず、コンポーネントが再レンダリングされるたびに毎回実行されてしまう。Reactは初回以降は以前の状態を保持するため、この二回目以降のexpensiveCalculation()の計算結果は無視されることになる。つまり、関数は実行されるものの、その結果は使われないため、コンピューターのリソースが無駄に消費され、アプリケーションのパフォーマンスが低下する原因となる。

このような無駄な計算を避け、アプリケーションの性能を向上させるための有効な手法が、useStateの「遅延初期化」だ。遅延初期化では、初期値を直接渡す代わりに、「関数」を渡す。書き方はconst [count, setCount] = useState(() => expensiveCalculation());のようになる。この記述を見ると、初期値の部分がアロー関数と呼ばれる関数の定義になっていることがわかるだろう。Reactは、useStateにこのような関数が渡された場合、通常の初期化とは異なる挙動をする。それは、この初期化関数を「コンポーネントの初回レンダリング時にのみ」実行し、その関数の戻り値を状態の初期値として設定するというものだ。二回目以降の再レンダリング時には、この初期化関数は一切実行されない。これにより、重い計算が不要に繰り返されることがなくなり、コンピューターのリソースの節約とパフォーマンスの向上が実現する。

遅延初期化が特に有用な状況はいくつかある。例えば、ブラウザのlocalStoragesessionStorage、またはクッキーといった外部のストレージからデータを読み込んで、それをコンポーネントの状態の初期値としたい場合だ。これらの読み込み処理は、ファイルやストレージへのアクセスを伴うため、決して軽い操作ではない。また、非常に複雑なデータ構造を構築したり、大きなJSONデータを解析したり、大量のデータを集計するといった、計算自体が重いケースでも有効だ。例えば、ユーザーが選択したテーマ(ライトモードかダークモードか)をlocalStorageに保存し、次にページを開いたときもそのテーマが維持されるようにするような場合が該当する。

具体的な例として、テーマの永続化を見てみよう。

1import { useEffect, useState } from "react";
2
3function App() {
4  const [theme, setTheme] = useState<string>(
5    () => localStorage.getItem("theme") || "light"
6  );
7
8  useEffect(() => {
9    localStorage.setItem("theme", theme);
10  }, [theme]);
11
12  // ... その他のコンポーネントの表示やボタンなどのコード ...
13}

このコードでは、useState() => localStorage.getItem("theme") || "light"という初期化関数を渡している。この関数内のlocalStorage.getItem("theme")という処理は、Appコンポーネントが最初に画面に表示されるとき(初回レンダリング時)にだけ実行される。もし、これを遅延初期化を使わずにconst [theme, setTheme] = useState(localStorage.getItem("theme") || "light");と記述した場合、テーマを変更するボタンがクリックされてsetThemeが呼び出され、コンポーネントが再レンダリングされるたびに、不要なlocalStorage.getItem("theme")が繰り返し実行されてしまう。これは、特に頻繁に再レンダリングされるコンポーネントでは、アプリケーションの動作が遅くなる原因となり得る。

遅延初期化を利用するメリットは主に三つある。第一に、パフォーマンスの向上だ。不要な重い計算や外部からのデータ読み込みがなくなるため、コンポーネントの再レンダリングがより高速になる。第二に、効率性が高まる。CPUの処理が無駄に消費されることがなくなり、アプリケーション全体として必要なリソースの使用量が最適化される。第三に、コードの意図がより明確になる。初期値の計算が一度だけ行われるべきであることがコードから読み取れるため、開発者がコードを理解しやすくなる。

しかし、全ての状況で遅延初期化を使うのが良いわけではない。例えば、useState(0)useState("")useState([])のように、初期値が単純な数値、文字列、空の配列などで、計算コストがほとんどかからない場合は、遅延初期化を使用する必要はない。むしろ、初期値を関数で囲むことでコードがわずかに複雑になり、その記述のオーバーヘッドに見合うメリットはないからだ。遅延初期化は、あくまで初期値の計算が「重い」という特定のシナリオに特化した最適化の手法であることを理解しておくことが重要だ。

結論として、useStateの遅延初期化は、アプリケーションの初期化段階で時間のかかる処理や外部へのアクセスが必要な場合に、その処理を初回レンダリング時にのみ実行させることで、不要なリソースの消費を避け、アプリケーションのパフォーマンスを大幅に改善できる非常に有効なテクニックだ。localStorageからのデータの読み込み、複雑な計算、あるいは複雑なデータ構造の初期設定を行う際には、この遅延初期化の活用を積極的に検討すべきだ。一方で、初期値が単純な場合は過度な最適化を避け、シンプルな記述を保つことが賢明である。この知識を適切に使いこなすことで、より効率的で高性能なReactアプリケーションを構築できるだろう。

関連コンテンツ

関連IT用語