useStateでコンポーネントの状態を管理しよう

学習の目標

本章では、以下の内容を学習します。

  • Stateの基本概念と役割を理解する
  • useStateフックの基本的な使い方を習得する
  • コンポーネントの再描画の仕組みを理解する
  • prevを使った状態更新の安全な方法を学ぶ
  • カウンターアプリの実装を通じて実践的な活用方法を習得する

はじめに

これまでは静的なコンポーネントを作成してきましたが、実際のWebアプリケーションでは、ユーザーの操作に応じて画面の内容が変わる必要があります。

例えば、ボタンをクリックしたら数字が増えたり、フォームに入力した内容が画面に反映されたりといった動的な処理です。こうした「変化する値」を管理するための仕組みが State(状態) です。

ReactではStateを管理するためにuseStateというフックを使います。フックとは、Reactの機能を関数コンポーネントで使えるようにするための特別な関数のことです。

それでは、具体的な例を見ながらuseStateの使い方を学んでいきましょう。

Stateとは何か

まず、Stateという概念について理解しましょう。Stateとは、コンポーネントが持つ「変化する可能性のあるデータ」のことです。

例えば、以下のような値がStateとして管理されます。

  • カウンターの現在の数値
  • ユーザーがフォームに入力したテキスト
  • モーダルの開閉状態
  • 商品の在庫数

これらの値は、ユーザーの操作やアプリケーションの処理によって変化し、その変化に応じて画面も更新される必要があります。

通常のJavaScriptの変数とは異なり、Stateが変更されると、Reactは自動的にコンポーネントを再描画(再レンダリング)して、新しい値を画面に反映してくれます。これがReactの大きな特徴の一つです。

useStateの基本的な使い方

それでは、実際にuseStateを使ってカウンターアプリを作ってみましょう。

これまでのプロジェクトを引き続き使用します。

新しいファイルsrc/Counter.jsxを作成し、以下のコードを入力してください。

import { useState } from 'react';

function Counter() {
  // useStateでStateを定義
  const [count, setCount] = useState(0);

  return (
    <div className="p-6 max-w-sm mx-auto bg-white rounded-lg shadow-md">
      <h2 className="text-2xl font-bold mb-4 text-center">カウンター</h2>
      <div className="text-4xl font-bold text-center mb-6 text-blue-600">
        {count}
      </div>
      <div className="flex gap-4 justify-center">
        <button
          onClick={() => setCount(count + 1)}
          className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
          +1
        </button>
        <button
          onClick={() => setCount(count - 1)}
          className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
        >
          -1
        </button>
      </div>
    </div>
  );
}

export default Counter;

このコードを理解するために、重要な部分を一つずつ見ていきましょう。

useStateの基本構文

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

この一行で、Stateを定義しています。 useStateは配列を返し、その配列を分割代入で受け取っています。

  • count: 現在のState値(最初は0)
  • setCount: State値を更新するための関数
  • useState(0): 初期値として0を設定

この書き方は配列の分割代入と呼ばれるJavaScriptの記法です。 useStateは常に2つの要素を持つ配列を返すため、このような書き方が一般的です。

State値の表示

<div className="text-4xl font-bold text-center mb-6 text-blue-600">
  {count}
</div>

JSXの中で{count}とすることで、現在のState値を画面に表示しています。 Stateが変更されるたびに、この部分も自動的に更新されます。

State値の更新

<button
  onClick={() => setCount(count + 1)}
  className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
  +1
</button>

ボタンがクリックされると、setCount(count + 1)が実行されます。 これにより、現在のcountの値に1を加えた新しい値でStateが更新されます。

次に、このコンポーネントを使うためにApp.jsxを更新しましょう。

import Counter from './Counter'

function App() {
  return (
    <div className="min-h-screen bg-gray-100 py-8">
      <div className="container mx-auto">
        <h1 className="text-3xl font-bold text-center mb-8">React State の練習</h1>
        <Counter />
      </div>
    </div>
  )
}

export default App

開発サーバーを起動して、ブラウザで確認してみましょう。

npm run dev

ボタンをクリックすると、数字が増減することが確認できるはずです。これがReactにおけるStateの基本的な動作です。

スクリーンショット

状態の更新と再描画の仕組み

ここで、Reactがどのようにして画面を更新しているかを理解しましょう。

  1. ユーザーがボタンをクリック
  2. setCount関数が実行される
  3. Reactが新しいState値を受け取る
  4. コンポーネント全体が再実行(再レンダリング)される
  5. 新しいState値で画面が更新される

このプロセスを「再レンダリング」と呼びます。 重要なのは、Stateが変更されるとコンポーネント関数全体が再実行されるということです。

これを確認するために、コンポーネントにconsole.logを追加してみましょう。

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

  // 再レンダリングを確認するためのログ
  console.log('Counterコンポーネントが実行されました。現在のcount:', count);

  return (
    // 以下は既存のコードと同じ
  );
}

ブラウザの開発者ツールのコンソールを開き、ボタンをクリックしてみてください。 ボタンをクリックするたびに、ログが出力されることが確認できるはずです。

Counterコンポーネントが実行されました。現在のcount: 0
Counterコンポーネントが実行されました。現在のcount: 1
Counterコンポーネントが実行されました。現在のcount: 2

このように、Stateが更新されるとコンポーネント全体が再実行され、最新のState値で画面が更新されます。

prevを使った安全な状態更新

先ほどのコードではsetCount(count + 1)という方法でStateを更新しました。しかし、より安全で推奨される方法があります。それが前の値(prev)を使った更新方法です。

Counter.jsxのボタン部分を以下のように修正してみましょう。

<div className="flex gap-4 justify-center">
  <button
    onClick={() => setCount(prev => prev + 1)}
    className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
  >
    +1
  </button>
  <button
    onClick={() => setCount(prev => prev - 1)}
    className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
  >
    -1
  </button>
</div>

先ほどまでとの違いは、setCountに関数を渡している点です。この関数の引数(prev)には、Reactが管理している最新のState値が自動的に渡されます。

なぜprevを使う方が良いのか

prevを使う方法が推奨される理由は、State更新の競合状態を避けるためです。

例えば、短時間で複数回ボタンがクリックされた場合、従来の方法では古いcountの値を参照してしまう可能性があります。 しかし、prevを使う方法では、常に最新のState値を基に更新できるため、より確実です。

また、この書き方に慣れることで、より複雑なState管理にも対応できるようになります。

難しく感じるかもしれませんが、基本的にはこちらの書き方を使うことをお勧めします。

より複雑な状態更新

カウンターに「リセット」機能と「5ずつ増減」機能を追加してみましょう。Counter.jsxを以下のように拡張してください。

import { useState } from 'react';

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

  // リセット機能
  const handleReset = () => {
    setCount(0);
  };

  // 5ずつ増加
  const handleAddFive = () => {
    setCount(prev => prev + 5);
  };

  // 5ずつ減少
  const handleSubtractFive = () => {
    setCount(prev => prev - 5);
  };

  return (
    <div className="p-6 max-w-sm mx-auto bg-white rounded-lg shadow-md">
      <h2 className="text-2xl font-bold mb-4 text-center">カウンター</h2>
      <div className="text-4xl font-bold text-center mb-6 text-blue-600">
        {count}
      </div>

      {/* 基本的な増減ボタン */}
      <div className="flex gap-2 justify-center mb-4">
        <button
          onClick={() => setCount(prev => prev + 1)}
          className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
          +1
        </button>
        <button
          onClick={() => setCount(prev => prev - 1)}
          className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
        >
          -1
        </button>
      </div>

      {/* 大きな増減ボタン */}
      <div className="flex gap-2 justify-center mb-4">
        <button
          onClick={handleAddFive}
          className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
        >
          +5
        </button>
        <button
          onClick={handleSubtractFive}
          className="bg-orange-500 text-white px-4 py-2 rounded hover:bg-orange-600"
        >
          -5
        </button>
      </div>

      {/* リセットボタン */}
      <div className="flex justify-center">
        <button
          onClick={handleReset}
          className="bg-gray-500 text-white px-6 py-2 rounded hover:bg-gray-600"
        >
          リセット
        </button>
      </div>
    </div>
  );
}

export default Counter;

このコードでは、イベントハンドラー関数を別途定義しており、以下の機能を追加しています。

  • リセットボタン: カウントを0に戻す
  • 5ずつ増加ボタン: カウントを5増やす
  • 5ずつ減少ボタン: カウントを5減らす これにより、より複雑な状態更新の方法を学ぶことができます。 開発サーバーを再起動して、ブラウザで確認してみてください。
npm run dev

スクリーンショット

useState は React の中で最も基本的で重要なフックの一つです。 今回はカウンターアプリを通じて、Stateの基本的な使い方と更新方法を学びました。 Stateを使うことで、ユーザーの操作に応じて動的に画面を更新することが可能になります。

ただ、Stateを使う際にはいくつかの注意点がありますので、次のセクションでそれらを確認しましょう。

Stateを使う時の注意点

Stateを直接変更してはいけない

以下のような書き方は絶対にしてはいけません

// ❌ 間違った例
count = count + 1; // Stateを直接変更している

// ✅ 正しい例
setCount(prev => prev + 1); // setCount関数を使用

Stateは イミュータブル(不変) であるべきです。 必ずsetCountのような更新関数を使ってStateを変更しましょう。

前者のような書き方で直接変更を行うと、Reactの再レンダリングの仕組みが正しく働かず、画面が更新されない可能性があります。

State更新は非同期

State更新は非同期で行われるため、setCountを呼んだ直後にcountの値を参照しても、まだ古い値のままです。

const handleClick = () => {
  setCount(prev => prev + 1);
  console.log(count); // まだ古い値が表示される
};

最新の値を取得したい場合は、useEffectというフックを使う必要がありますが、それは次の章で学習します。

まとめ

本章では、ReactのStateについて学習しました。 以下のポイントを理解できたことと思います。

  • Stateはコンポーネントが持つ「変化する可能性のあるデータ」である
  • useStateフックを使ってStateを定義し、管理する
  • Stateが更新されると、コンポーネントが自動的に再レンダリングされる
  • prevを使った更新方法がより安全で推奨される
  • Stateは直接変更せず、必ず更新関数を使用する

Stateは React アプリケーションの核となる概念です。 この基本をしっかりと理解することで、より複雑な機能を持つアプリケーションも作れるようになります。 次の章では、さらに実践的なStateの活用方法を学んでいきましょう。

このセクションは有料サブスクリプションへの登録、またはログインが必要です。完全なコンテンツにアクセスするには、料金ページ(/pricing)をご覧ください。購入済みの場合は、ログインしてください。

Basicプランでより詳しく学習

この先のコンテンツを読むにはBasicプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。

作成者:とまだ
Previous
Stateとライフサイクル