Reactで画面の状態を持つときは、useState を使います。
const [count, setCount] = useState(0);count が今の値、setCount が値を更新するための関数(set関数)です。この記事では、set関数の使い方と、つまずきやすいところをまとめます。
set関数とは
useState は、今の値と、それを更新する関数の2つを配列で返します。値を変えるときは、count = 1 のように直接代入せず、必ず set関数を使います。set関数を呼ぶと、Reactが新しい値で画面を描き直します。
名前に決まりはありませんが、「set + 変数名」にするのが一般的です。
| 変数 | set関数 |
|---|---|
count | setCount |
user | setUser |
isModalOpen | setIsModalOpen |
更新の2つの書き方
新しい値をそのまま渡す
前の値と関係なく、新しい値にするときは、その値を渡します。
setCount(0); // 0 に戻す
setName('React太郎'); // 入力された文字にする前の値を使うときは関数を渡す
「今の値に1を足す」のように、前の値をもとにするときは、関数を渡します。Reactが、その時点の最新の値を引数に入れて呼んでくれます。
setCount(prev => prev + 1);違いが出るのは、同じ処理の中で何度も更新するときです。
// count が 0 のときにクリックすると…
function handleClickA() {
setCount(count + 1); // 0 + 1
setCount(count + 1); // この行でも count は 0 のままなので 0 + 1
setCount(count + 1); // 0 + 1
// 結果は 1
}
function handleClickB() {
setCount(prev => prev + 1); // 0 → 1
setCount(prev => prev + 1); // 1 → 2
setCount(prev => prev + 1); // 2 → 3
// 結果は 3
}関数を渡しておけば、このようなずれが起きません。前の値を使うときは、関数で書くと覚えておくと安全です。
渡した関数は、開発中(StrictMode)に2回呼ばれることがあります。中で通信やログの書き込みなどをしないで、新しい値を計算して返すだけにします。
オブジェクトを更新する
Reactは、set関数に渡された値が前と同じものかどうかで、描き直すかを決めます。オブジェクトの中身だけを書き換えて同じオブジェクトを渡すと、「変わっていない」と判断されて、画面が更新されません。
const [user, setUser] = useState({ id: 1, name: '太郎' });
// × 中身を直接書き換えて、同じオブジェクトを渡している
user.name = '次郎';
setUser(user); // 画面は更新されない
// ○ スプレッド構文で、name だけ変えた新しいオブジェクトを作る
setUser(prev => ({ ...prev, name: '次郎' }));関数でオブジェクトを返すときは、({ ... }) のように丸かっこで囲みます。囲まないと { } が関数の中身とみなされます。
入れ子のオブジェクトは中もコピーする
スプレッド構文(...)がコピーするのは1段目だけです。中のオブジェクトを変えるときは、そこも新しく作ります。
// user = { name: '太郎', address: { city: '札幌市', zip: '060-0000' } }
setUser(prev => ({
...prev,
address: { ...prev.address, city: '大阪市' },
}));配列を更新する
配列も同じで、push や splice のように元の配列を変えるメソッドは使わず、新しい配列を作って渡します。
// 追加する
setItems(prev => [...prev, newItem]);
// 削除する(id が一致しないものだけ残す)
setItems(prev => prev.filter(item => item.id !== id));
// 1つだけ変える(その要素だけ新しいオブジェクトにする)
setItems(prev =>
prev.map(item => (item.id === id ? { ...item, done: !item.done } : item))
);| やりたいこと | 使わない | 使う |
|---|---|---|
| 追加 | push | [...prev, x] |
| 削除 | splice | filter |
| 変更 | prev[i] = x | map |
| 並べ替え | sort | [...prev].sort() または toSorted() |
set関数を呼んだ直後は、まだ古い値
function handleClick() {
setCount(10);
console.log(count); // 10 ではなく、更新前の値が出る
}これは set関数が「非同期だから」と説明されることがありますが、正確には少し違います。count は、その回の画面を描いたときの値で決まっていて、その関数の中では変わりません。setCount(10) は「次に画面を描くときは 10 にしてね」と予約するだけで、今の count を書き換えるものではありません。
新しい値をその場で使いたいときは、先に変数に入れておいて、それを使います。
function handleClick() {
const next = count + 1;
setCount(next);
// 新しい値を使う処理は next で行う
saveToServer(next);
}「値が変わったら何かする」のを useEffect で書く方法もありますが、クリックなどがきっかけの処理なら、上のようにその処理の中で書くほうが、いつ動くか分かりやすくなります。useEffect は、画面の外の仕組み(タイマーや通信の接続など)と合わせるときに使います。
TypeScript で型を付ける
初めの値から型が分かるとき(useState(0) なら number)は、書かなくても自動で付きます。null や空の配列で始めるときは、<> で型を指定します。
type User = { id: number; name: string };
type Item = { id: number; title: string; done: boolean };
const [user, setUser] = useState<User | null>(null); // まだ読み込んでいないときは null
const [items, setItems] = useState<Item[]>([]); // 空の配列から始める型を付けないと、useState([]) は「何も入らない配列」とみなされて、あとで要素を足そうとするとエラーになります。
まとめ
- 値は直接代入せず、set関数で更新する
- 前の値を使うときは
setCount(prev => prev + 1)のように関数を渡す - オブジェクトと配列は、中身を書き換えずに新しく作って渡す。入れ子は中もコピーする
- set関数を呼んだ直後は、まだ古い値。新しい値は先に変数に入れて使う
nullや空の配列で始めるときは、useState<型>で型を付ける
ほかの開発の記事は、開発・プログラミングメモにまとめています。



コメント