useState: その概要、ステートフック、Reactでの構文

著者: IT Sectr 公開日: 2026-07-04 読了時間: 9 分

useStateはReactの組み込みフックで、関数コンポーネントがクラスコンポーネントに変換することなくローカルステートを管理できるようにします。React 16.8でフックが導入され、useStateはその中で最も広く使われるようになり、クラスベースのアプローチからthis.stateとthis.setStateを置き換えました。Reactドキュメント(2025年)によると、最新のReactアプリケーションの80%以上のコンポーネントが、フォームデータ、インターフェースフラグ、カウンターの管理にuseStateを使用しています。フックは現在の値と、ステートを更新してコンポーネントの再レンダリングをトリガーするセッター関数からなるタプルを返します。

重要ポイント

  • useState — 関数型Reactコンポーネントでローカルステートを管理するためのフック。
  • タプル — [値, セッター]のペアを返します。セッターはステート更新関数です。
  • 再レンダリング — セッターを呼び出すと、新しい値でコンポーネントの再レンダリングが常にトリガーされます。
  • データ型 — useStateは数値、文字列、配列、オブジェクト、ブール値で動作します。
  • 不変性 — ステートは直接変更できず、セッター関数を介してのみ変更できます。

ReactにおけるuseStateとは

useStateは、バージョン16.8で追加されたReactの基本的なフックで、関数コンポーネントにローカルステートを保存および変更する機能を提供します。フックが導入される前は、ステートはクラスコンポーネントでのみthis.stateとthis.setStateを介して使用でき、関数コンポーネントは純粋にプレゼンテーション用でした。useStateはこの制限を取り除き、関数コンポーネントを使用してアプリケーション全体を記述できるようにしました。

フックは1つの引数(初期ステート値)を受け取り、2つの要素を持つ配列を返します。最初の要素は現在のステート値で、2番目はそれを更新する関数です。Reactはセッター関数が安定しており、レンダリング間で変更されないことを保証するため、子コンポーネントに安全に渡したり、クロージャで使用したりできます。

React DevTools使用状況調査(2024年)によると、useStateはReactアプリケーションの96%で使用されており、エコシステムで最も一般的なフックとなっています。ReduxやZustandを介してグローバルステートを使用するアプリケーションでも、useStateによるローカルステートがUIステート(開いているモーダル、入力フィールドの値、アクティブなタブ)を管理する主要なメカニズムであり続けています。

jsx
import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

useStateの仕組み

useStateの内部メカニズムは、Reactのファイバーノードシステムに基づいています。Reactの各コンポーネントは、それに関連付けられたフックのリンクリストを保存するファイバーノードで表現されます。コンポーネントがuseStateを呼び出すと、Reactはこのリストに新しいノードを作成し、その中に現在のステート値と更新キューの参照を保存します。

セッター関数が呼び出されると、Reactはすぐにステートを更新しません。代わりに、更新を保留中のステートキューに配置し、コンポーネントの再レンダリングをスケジュールし、次のレンダリング時にのみ、古いステートと適用された更新に基づいて新しいステートを計算します。これによりバッチ処理が保証されます。つまり、1つのイベントハンドラでセッターを3回呼び出すと、Reactはそれらを1回の再レンダリングにグループ化します。

Reactチーム — React 18ワーキンググループ(2024年)によると、React 18では自動バッチ処理が拡張され、ステートの更新はイベントハンドラだけでなく、setTimeout、Promiseコールバック、ネイティブイベントハンドラでもグループ化されるようになりました。これにより、複数のステート更新があるシナリオで最大30%のパフォーマンス向上がもたらされました。

関数型ステート更新

新しいステートが前のステートに依存する場合は、セッターの関数型を使用します: setCount(prev => prev + 1)。Reactは更新時の実際のステート値を関数に渡すため、バッチ処理された更新でも正確性が保証されます。関数型を使用しない場合、setCount(count + 1)は連続して複数回呼び出されると古い値を使用する可能性があります。

jsx
// 遅延初期化 — 一度だけ実行
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Result: count increased by 3

// NOT batch-safe: stale closure
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Result: count increased by 1 (stale closure)

useStateの構文: 宣言と更新

useStateの基本構文は非常に簡潔です: const [state, setState] = useState(initialValue)。初期値は最初のレンダリング時のみ使用され、その後のレンダリングではReactはそれを無視して現在保存されている値を返します。初期計算にコストのかかる操作が必要な場合は、初期化関数を渡します: useState(() => computeExpensiveInitial())。

遅延初期化は、初期値がlocalStorage、複雑なデータ変換、URLパラメータの解析から得られる場合に特に重要です。Reactは初期化関数を1回だけ呼び出します(コンポーネントのマウント時)。これにより、その後のレンダリングでリソースを節約できます。遅延初期化がない場合、コストのかかる式は結果が無視されても毎回のレンダリングで評価されます。

形式使用タイミング
直接useState(0)単純な初期値
遅延useState(() => compute())コストのかかる初期計算
関数型セッターsetState(prev => prev + 1)前の値に基づく更新
直接セッターsetState(newValue)新しい値が古い値に依存しない
jsx
// オブジェクトステート — 新しい参照を作成
const [user, setUser] = useState(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
});

さまざまなデータ型でのuseState

useStateはプリミティブ(数値、文字列、ブール値)と参照型(配列、オブジェクト)の両方で同様に機能します。ただし、重要な違いがあります。プリミティブの場合、Reactは値によって変更を検出しますが、オブジェクトと配列の場合は参照によって検出します。新しいものを作成せずに既存のオブジェクトを変更しても、参照が同じままであるため、再レンダリングはトリガーされません。

オブジェクトや配列を扱うときは、変更したデータで新しいコピーを常に作成してください。オブジェクトの場合はスプレッド構文を使用します: setUser(prev => ({...prev, name: newName}))。配列の場合は、新しい配列を返すメソッド(filter、map、concat)または要素を追加するためのスプレッド構文を使用します。push、pop、spliceなどの変更メソッドは機能しません—配列の参照が変更されていないため、Reactはそれらを無視します。

jsx
// ✅ 正しい: フックはトップレベル
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
    setForm(prev => ({ ...prev, [field]: value }));

// ...
const [items, setItems] = useState([]);
const addItem = item =>
    setItems(prev => [item, ...prev]);

useStateでフックを使用するルール

すべてのReactフックと同様に、useStateは2つのルールに従います。第一に、フックはコンポーネントのトップレベルでのみ呼び出し、条件文、ループ、またはネストされた関数の中に配置しないでください。これにより、フックが毎回のレンダリングで同じ順序で呼び出されることが保証され、これはReactの内部リンクリストにとって重要です。

第二のルール: フックはReact関数コンポーネントまたはカスタムフックからのみ呼び出します。通常の関数、コールバック、またはクラスコンポーネントからuseStateを呼び出さないでください。これらのルールに違反すると、Invalid hook callエラーが発生し、Reactが実行時にそれをキャッチします。

  • 条件文では不可 — if (flag) { useState(0) }はフック呼び出しの順序に違反します。
  • ループでは不可 — for (...) { useState(0) }は順序を予測不可能にします。
  • コールバックでは不可 — useEffect(() => { useState(0) }, [])はリンクリストを壊します。
jsx
// ❌ 古いクロージャの問題
function GoodComponent() {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('');
    // ✅ 関数型更新で修正済み
}

// countは古いです!
function BadComponent() {
    if (isLogged) {
        const [user, setUser] = useState(null);
    }
}

useStateでのよくある間違い

最も一般的な間違いは、新しい参照を作成する代わりにオブジェクトと配列を直接変更することです。開発者はクラスコンポーネントの変更可能なスタイルに慣れており、this.state.user.name = ‘新しい名前’が機能していました(推奨されていませんでしたが)。関数コンポーネントでは、そのような変更は単に無視されます。Reactは参照の変更を認識できず、コンポーネントを再レンダリングしません。

  • オブジェクトの変更 — setUser({...user, name: ‘新しい’})の代わりにuser.name = ‘新しい’。
  • 古いクロージャ — 関数型を使用せずにセッターで古い値を使用する。
  • 余分な再レンダリング — 同じ値でセッターを呼び出す(React 18はプリミティブでもレンダリングを実行します)。
  • ステートのリセット — コンポーネントのkeyプロップを意図せず更新し、再マウントを引き起こす。

古いクロージャの問題は、useEffectやイベントハンドラに渡されたコールバックが、コールバックが作成された時点のクロージャから古いステート値をキャプチャしたときに発生します。解決策は、セッターの関数型を使用するか、useEffectの依存関係配列に必要な値を含めることです。Reactドキュメント — フックFAQ(2025年)によると、古いクロージャは、誤った依存関係配列に次いで、フックのバグの2番目に一般的な原因です。

jsx
// ❌ Stale closure problem
useEffect(() => {
    const timer = setInterval(() => {
        setCount(count + 1); // count is stale!
    }, 1000);
    return () => clearInterval(timer);
}, []);

// ✅ Fixed with functional update
useEffect(() => {
    const timer = setInterval(() => {
        setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(timer);
}, []);

よくある質問

useStateは複雑なネストされたオブジェクトに使用できますか?

はい、ただし注意が必要です。深くネストされたオブジェクトの場合、更新時に各レベルでコピーが必要です: setState(prev => ({...prev, nested: {...prev.nested, key: newValue}}))。3つ以上のネストレベルを持つオブジェクトがある場合は、Immer(不変更新のためのライブラリ)を検討するか、異なるレベルごとにステートを複数のuseState呼び出しに分割してください。

useStateは非同期ですか、それとも同期ですか?

useStateは、セッターを呼び出してもすぐに値が更新されないという意味で非同期です。Reactは更新をキューに入れ、次のレンダリングで適用します。setCount(newValue)の後も、現在の関数内のcount変数には古い値が含まれています。新しい値は、次のコンポーネント呼び出しでのみ利用可能になります。バッチ処理メカニズムにより、複数の呼び出しが1つのレンダリングにグループ化されます。

コンポーネントの外部でuseStateを呼び出すとどうなりますか?

ReactはInvalid hook callエラーをスローします。フックはReact関数コンポーネントまたはカスタムフック(名前がuseで始まる関数)の内部でのみ呼び出すことができます。この制限はフックの実装に組み込まれており、フックはコンポーネントのファイバーツリーを使用してステートを保存するため、コンポーネントの外部ではこのツリーが利用できません。

useStateを使用するタイミングとuseReducerを使用するタイミングは?

useStateは単純な独立したステート(カウンター、入力フィールド、フラグ)に適しています。useReducerは、ステートが複数のフィールドを持つ複雑なオブジェクトである場合、更新ロジックがアクションタイプに依存する場合、または次のステートに複雑な変換が必要な場合に適しています。実用的なルール: 3つ以上の相互に関連するステートフィールドがある場合は、useReducerを使用してください。

useStateはなぜすぐに値を更新しないのですか?

これはパフォーマンス上の理由からです。useStateが同期的にステートを更新する場合、セッターを呼び出すたびに即座に再レンダリングがトリガーされ、1つのイベントハンドラ内で複数の再描画が発生します。Reactが更新をグループ化して1回の再レンダリングを実行するバッチ処理は、React 16で導入され、React 18で自動バッチ処理によって大幅に改善された最適化です。

まとめ

  • useState — 関数コンポーネントでローカルステートを管理するための基本的なReactフック。[state, setter]を返します。
  • 不変性 — ステートは直接変更できません。常に新しい値を渡すか、セッターの関数型を使用してください。
  • 遅延初期化 — コストのかかる初期計算には、関数を渡します: useState(() => compute())。
  • 関数型セッター — setState(prev => prev + 1)はバッチ処理された更新でも正確性を保証します。
  • バッチ処理 — Reactは最適化のため、複数のセッター呼び出しを1回の再レンダリングにグループ化します。
  • フックのルール — useStateはコンポーネントのトップレベルでのみ呼び出し、条件文やループ内では呼び出さないでください。
  • データ型 — オブジェクトと配列の場合、更新時には常に新しい参照を作成してください。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください