React: Stateの基本:useState

最終更新:2026-10-26

ステートとは、コンポーネントのメモリのことです。プロップが他者から渡されるパラメータ(読み取り専用)であるのに対し、ステートは自分専用の小さなノート(編集可能)のようなものです。useStateは、Reactがコンポーネントに組み込む「メモリチップ」のようなものです。


1. 学習内容



2. カウンターが引き起こした考察

(1) 課題:通常の変数ではUIの更新がトリガーされない

ボブは簡単なカウンターを作りたいと思っています。ボタンをクリックすると、数字が1ずつ増えるようなものです。

▶ 試してみよう

ボブはある問題に気づきました。通常の変数では、ReactにUIを更新するよう通知されないのです。コンポーネントが再レンダリングされるたびに、countは0にリセットされてしまいます。

(2) useState を用いた解決策

▶ 試してみよう

戻り値:useState は配列 [current value, setter function] を返します。setCount を呼び出した後、React は自動的にコンポーネントを再レンダリングし、最新の値を表示します。



3. useState の基本概念

(1) 分割代入

JSX
import { useState } from 'react'

// useState(initialValue) Back [value, setValue]
const [count, setCount] = useState(0)
//      ^      ^          ^
//      |      |          └─ Initial value(Applies only to the first render)
//      |      └─ Update Function(Triggers a re-render after being called)
//      └─ Current status value(Get the latest value on every render)
▶ 試してみよう
セクション 説明
count 現在の状態値。レンダリングのたびに最新の値が取得される
setCount update関数:これを呼び出すと → Reactがコンポーネントを再レンダリングする
useState(0) 初期値(最初のレンダリング時に使用され、それ以降のレンダリングでは無視される)

(2) 更新メカニズム

▶ 試してみよう

重要な概念:setCount(count + 1) は、「count を即座に変更する」のではなく、「次回のレンダリング時に count を更新するよう React に指示する」という意味です。これは 非同期更新 と呼ばれます。

(3) 関数型更新

新しい状態が古い状態に依存する場合は、関数型更新を使用する必要があります:

▶ 試してみよう
メソッド 構文 特徴
直接更新 setCount(count + 1) シンプルだが、連続して呼び出すとデータが失われる
機能アップデート setCount(prev => prev + 1) 正確;連続した呼び出しでも正常に動作する


4. 配列とオブジェクトの更新(不変性)

React では、状態を直接変更してはなりません。state.push() や state.name = 'xxx' は絶対に使用しないでください。代わりに、新しい配列またはオブジェクトを作成して、古い値と置き換えてください。

(1) 更新対象のオブジェクト

▶ 試してみよう

(2) 配列を更新する

▶ 試してみよう

出力:

TEXT
状態:arr

▶ サンプル:配列演算のクイックリファレンス表

JSX
// ============================================
// Example:React Array Operations in C 6 Common Scenarios
// ============================================

const [arr, setArr] = useState([1, 2, 3])

// 1. Append to the end
setArr([...arr, 4])           // [1, 2, 3, 4]

// 2. Add to the beginning
setArr([0, ...arr])           // [0, 1, 2, 3]

// 3. Insert in the middle
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
                              // [1, 99, 2, 3]

// 4. Delete Element(filter)
setArr(arr.filter(n => n !== 2))  // [1, 3]

// 5. Update Element(map)
setArr(arr.map(n => n === 2 ? 22 : n))  // [1, 22, 3]

// 6. Sort(Copy first, then sort)
setArr([...arr].sort((a, b) => b - a))  // [3, 2, 1]
▶ 試してみよう

出力:

TEXT
配列の状態操作:push → 項目の追加、filter → 項目の削除、map → 項目の更新。各操作は新しい配列参照を返すため React が再レンダリングされる


5. 複数の状態変数

コンポーネントには複数の状態変数を設定できます。それらを1つの巨大なオブジェクトに詰め込むのではなく、ロジックごとに分割することをお勧めします:

JSX
function RegistrationForm() {
  // ✅ Recommendations:Logical Breakdown
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [errors, setErrors] = useState({})

  // ❌ Not recommended:All states are stored in a single object
  const [form, setForm] = useState({
    name: '', email: '', password: '',
    isSubmitting: false, errors: {}
  })
  // Expand the entire object when updating,Fields that are easy to overlook
}
▶ 試してみよう
戦略 メリット デメリット
複数の useState に分割する 更新が正確で、型安全、かつ読みやすい 状態が多いと変数が多くなりすぎる
1つのuseStateにまとめられている 複数のフィールドを一度に更新するのに便利 更新の構文が煩雑で、展開し忘れることがよくある

推奨事項:独立した状態(入力フィールド、読み込みインジケーター、エラーメッセージなど)には複数の useState インスタンスを使用してください。論理的に関連するフィールド(ユーザープロファイル内のすべてのフィールドなど)は、1つのオブジェクトにまとめることができます。



6. 状態の引き上げ

複数のコンポーネントが同じ状態を共有する必要がある場合は、その状態を、それらに最も近い共通の親コンポーネントへと引き上げてください。

100%
graph TB
    subgraph "Before the status upgrade(Data is not shared)"
        A[Parent Component App] --- B[Child component A<br/>Have their own count]
        A --- C[Child component B<br/>Have their own count]
    end
    
    subgraph "After the status upgrade(Data Sharing)"
        D[Parent Component App<br/>**count Here**] --- E[Child component A<br/>Read count, Call setCount]
        D --- F[Child component B<br/>Read count, Call setCount]
    end

出力:

TEXT
状態:arr(setter: setArr)

▶ サンプル:状態のリフトアップの実践的な活用

▶ 試してみよう

出力:

TEXT
摂氏で 100 を入力 → 華氏で自動的に 212.0 が表示される。表示 "100°C = 212.0°F"。2 つの入力ボックスが同期している。


7. 完全な例:ショッピングカートのカウンター

▶ 実行例を展開(96 行)
▶ 試してみよう

出力:

TEXT
サブタイトル:"Temperature Converter"。表示:"Celsius(°C):"。状態:temperature(setter: setTemperature)

インタラクションの流れ:

  • 「+/−」をタップして数量を調整すると、小計と合計がリアルタイムで更新されます
  • ✕をクリックして商品を削除する。ショッピングカートが空のときは、わかりやすいメッセージを表示する
  • プロモーションコード REACT2026 を入力すると、20元割引になります

▶ サンプル:オブジェクトの状態の更新—ユーザー情報の編集

JSX
function UserEditor() {
  const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })

  function updateField(field, value) {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Edit Profile</h3>
      <input value={user.name} onChange={e => updateField('name', e.target.value)}
        placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input value={user.email} onChange={e => updateField('email', e.target.value)}
        placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
        placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
        {JSON.stringify(user, null, 2)}
      </pre>
    </div>
  )
}
▶ 試してみよう

出力:

TEXT
状態:address。入力フィールド:Street、City、ZIP

出力:

TEXT
表示:"Edit Profile"。状態:user(setter: setUser)。入力:Name、Email、Age

▶ サンプル:ネストされたオブジェクトの状態 — 住所管理

▶ 試してみよう

出力:

TEXT
配送先フォーム:Street、City、ZIP、国ドロップダウン(US/CN/JP)、"Primary address" チェックボックス。すべてのフィールドがリアクティブに状態を更新する。

出力:

TEXT
表示:"Shipping Address"。状態:address(setter: setAddress)。入力:Street、City、ZIP。ドロップダウン:United States、China、Japan

▶ サンプル:関数による更新でバッチ更新の問題を解決する

▶ 試してみよう

出力:

TEXT
Score: 0、Multiplier: x1。+10/+50/+100 は スコア×倍率 で加点。"2x Multiplier" は倍率を倍にする(最大 x8)。Reset → Score: 0、x1

❓ よくある質問

Q useStateと通常の変数では、パフォーマンスにどのような違いがありますか?
A useStateは追加のオーバーヘッド(変更の追跡、レンダリングのスケジューリング)を伴うため、「変更が発生した際にUIの更新がトリガーされる」ようなシナリオでのみ使用すべきです。変数が変更されても UI の更新を必要としない場合(タイマー ID やスクロール位置など)、useRef(第 10 課)を使用する方が適切です。
Q 1つのコンポーネント内にuseStateをいくつまで含めることができますか?パフォーマンス上の問題はありますか?
A 数に制限はありません。Reactの公式ドキュメントでは、1つの大きなオブジェクトを使用するのではなく、ロジックに基づいて複数のuseStateに分割することを推奨しています。各useStateの呼び出しは「状態単位」であり、Reactは多数のuseStateを効率的に処理できます。一般的なコンポーネントには、通常3~8個のuseStateが含まれます。
Q setState の更新は同期的ですか、それとも非同期的ですか?
A React 18 以前では、setState はイベントハンドラ内では「自動的にバッチ処理」されていましたが、setTimeout および Promise の呼び出し内では同期的に実行されていました。React 18では「自動バッチ処理」が導入されたため、setStateはイベントハンドラ、setTimeout、Promise、fetchのコールバック内で呼び出されるかどうかにかかわらず、非同期バッチ処理されるようになりました。更新されたDOMに同期的にアクセスする必要がある場合は、flushSync(() => setState(...))を使用できます。

📖 まとめ


📝 練習問題

  1. 基本演習(難易度 ⭐):クリックすると ❤️ と 🤍 の状態を切り替え、いいねの数を表示する LikeButton コンポーネントを作成してください。
  2. 上級演習(難易度 ⭐⭐):useState を使用して経費のリスト(説明、金額、日付)を管理し、項目の追加や削除に対応する ExpenseTracker コンポーネントを作成してください。
  3. 課題(難易度:⭐⭐⭐):3つのuseStateインスタンスを使用してR、G、Bのスライダー値(0~255)を管理し、リアルタイムの色プレビューと16進数のカラーコードを表示するColorPickerコンポーネントを作成してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%