はじめに
なんとなく理解しているつもりだった
React hooksをしっかり学びなおそうと思います。
目次
- そもそも「状態(state)」とは?
- useStateとは?
- useState の基本的な流れ
- なぜ必要なのか?
- よくあるパターン別コード例
- 使用上で注意すべきポイント
- まとめ
そもそも「状態(state)」とは?
React ではコンポーネント内で表示を行う際に、いろいろな情報を扱います。
たとえばユーザーが入力したテキスト、ボタンがクリックされた回数、API から取得してきたデータなどです。こういった情報は、コンポーネントで保持して表示内容を変えたり、他の処理をしたりする必要があります。その「コンポーネント内で変更されうる情報」を React では state(ステート) と呼びます。
- 例
- ユーザーがボタンを押すたびにカウントアップする値
- テキストボックスに入力された文字列
- API から取得したリストやオブジェクト
state はコンポーネントが「今どんな状態なのか」を示す重要な役割を持っており、この state が変わると、React はコンポーネントの UI を再レンダリングして画面表示を更新します。
useStateとは?
useState は React の関数コンポーネント内で状態を管理するためのフックです。useState を使うと、下記のような2つのものを受け取ることができます:
- 状態そのものを表す変数
- その状態を更新するための関数
どちらも一緒にワンセットで返ってくるイメージです。
使い方はシンプルで、コードの先頭で次のように import します.
import React, { useState } from 'react';
その後、関数コンポーネントの中で useStateを呼び出すと、次のように使えます.
import React, { useState } from 'react';
function ExampleCoomponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>現在のカウント : {count}<p/>
<button> onClick={() => setCount(count + 1)}>カウントアップ<button/>
<div/>
);
}
解説
- count が「現在の状態(数字)」
- setCount が「count の状態を更新するための関数」
- useState(0) で 初期値として 0 を指定している
という構造になっています。
useState の基本的な流れ
初期値を設定する
useState(初期値) の ( ) の中に、状態が最初に持つ値を入れます。
文字列、数値、配列、オブジェクト、ブール値など、基本的には何を入れても問題ありません。
// 数値型
const [count, setCount] = useState(0);
// 文字列型
const [text, setText] = useState("Hello");
// 配列
const [items, setItems] = useState([]);
// オブジェクト
const [userInfo, setUserInfo] = useState({ name: "", age: 0 });
// ブール値
const [isOpen, setIsOpen] = useState(false);
状態を使う
返ってきた最初の変数(count や text など)を、コンポーネントの JSX 内でそのまま使うことができます。変数が変われば、画面の表示も連動して変わります。
状態を更新する
返ってきた2番目の関数(setCount や setText など)を使って、状態を更新します。この更新関数を呼び出すと、自動的にコンポーネントが再レンダリングされ、最新の状態が画面に反映されます。
なぜ必要なのか?
変数を直接書き換えるだけではダメな理由
React の仕組み上、単純な変数を書き換えても画面に自動で反映されません。
たとえば JS の普通の変数に let num = 0; としておいて、
ボタンを押すたびに num++ しても、画面は変わらないのです。
React では「データが変わったら再レンダリングをして画面を更新する」仕組みを使います。その仕組みに React が気付くためには、useState のような Hooks を使って状態を管理する必要がある、というわけです。
setState のタイミング
setCount のような更新関数を呼ぶと、React は「状態が変わった」と判断し、コンポーネントを再度描画します。すると、JSX 内の表示が最新の状態に合わせて切り替わります。この仕組みのおかげで、状態管理がシンプルかつ分かりやすく記述できるようになっています。
よくあるパターン別コード例
カウンター
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleDecrement = () => {
setCount(count - 1);
};
return (
<div>
<h2>カウント: {count}</h2>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
</div>
);
}
export default Counter;
-「+」ボタンを押すと count + 1
- 「-」ボタンを押すと count - 1
- 結果は count の更新に応じて画面に反映される
テキストボックスと連動
import React, { useState } from 'react';
function TextInput() {
const [text, setText] = useState("");
const handleChange = (event) => {
setText(event.target.value);
};
return (
<div>
<input
type="text"
value={text}
onChange={handleChange}
placeholder="文字を入力してください"
/>
<p>入力した文字: {text}</p>
</div>
);
}
export default TextInput;
- value 属性に text を指定
- onChange イベントで event.target.value を setText へ渡す
- text が更新されるたびに下の
も変化していく
#### 配列やオブジェクトを管理する
import React, { useState } from 'react';
function TodoList() {
const [task, setTask] = useState("");
const [tasks, setTasks] = useState([]);
const handleAddTask = () => {
// 新しいタスクを配列に追加
setTasks([...tasks, task]);
// タスク名をリセット
setTask("");
};
return (
<div>
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
/>
<button onClick={handleAddTask}>追加</button>
<ul>
{tasks.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default TodoList;
解説
setTasks([...tasks, task]) の意味
- [...] はスプレッド構文 と呼ばれるもので、配列(またはオブジェクト)を展開しコピーするために使います
- ここでは tasks という 既存の配列 を複製し、新しい要素として task 変数 を末尾に追加しています
- つまり、tasks が [ "買い物に行く", "宿題をする" ] のような配列だった場合、[...tasks, task] は [ "買い物に行く", "宿題をする", taskに入っている文字列 ] のような新しい配列を作ります
- React では、直接配列を変更する(破壊的変更) のではなく、新しい配列を作って setTasks に渡すことで「以前とは別の配列だ」と認識させ、再レンダリングをトリガーします
setTask("") の意味
- これは テキスト入力用の状態をリセット しているコードです
- フォームに入力していた文字列を空文字列 "" にすることで、テキストボックスをクリア する目的があります
- たとえばユーザーが入力フォームに「りんごを買う」と打ち込んで 追加ボタン を押した際に、
- setTasks([...tasks, "りんごを買う"]); で配列に新しいタスクを追加
- setTask(""); でテキストボックスの値を ""(空文字)にしているので、次の入力がしやすくなる
という流れです。
key={index}
- これは React のルールで、リストをレンダリングするときには一意の key を付ける 必要があります
- key は内部的に要素を識別するための「ID」のようなものです
- ここでは単純化のために index(要素のインデックス)を使っていますが、できれば重複しないユニークな ID を使うのが望ましいです
- たとえば、データが持っている固有の id を使うケースが多いです
- index を使うと要素が並び替わったときに追跡できなくなるなどの問題がある可能性があります
使用上で注意すべきポイント
- Hooks は関数コンポーネントのトップレベルで呼び出す
- 条件分岐やループの中で呼び出すと、React が状態を正しく管理できなくなる可能性があります
- 状態を直接変更しない(immutability)
- 状態は常に setXxx というアップデート関数を介して変更し、配列やオブジェクトを扱う場合はコピーを作ってから変更するのが基本です
- 複数の useState を使うことができる
- 1つのコンポーネントの中で、いくつも useState を宣言して OK です。必要に応じて状態を細かく分割すると管理がしやすくなります
- 非同期であること
- setState はすぐに状態を変更しているように見えて、実際には React の都合でまとめて更新されることがあります。そのため、最新の状態を使いたい場合はコールバック形式の setCount(prev => prev + 1) などを利用するのが望ましいケースもあります
まとめ
- useState は React の Hooks の一つで、関数コンポーネントに「状態管理」を簡単に導入するための仕組み
- [現在の状態, 状態を更新する関数] = useState(初期値) の形で呼び出し、UI が状態に応じて自動的に再レンダリングされる
- 状態を変えるには setXxx を使う。変数を直接変更しても React が認識できず、画面は変化しない
- 配列やオブジェクトなど様々な型を扱うことができるが、更新時には イミュータブルに操作 する (コピーしてから変更する) のが基本
- React の中核的な考え方である「状態(state)」を理解し、正しく管理することで、より動的で使いやすいユーザーインターフェイスを作ることができる