はじめに
CodexでTODOアプリなどを作りながら、Reactの機能を一つずつ整理しています。
今回は、useState の使い方と状態更新の仕組みについて、備忘録としてまとめます。
構文
useState は、コンポーネント内で状態(State)を管理するためのHookです。
const [状態, 状態を変更する関数] = useState(初期値);
TODOアプリでは、たとえば次のように定義できます。
const [todos, setTodos] = useState<Todo[]>([]);
この場合、todos には初期値として空の配列が入ります。
状態を更新したいときは、todos を直接変更するのではなく、setTodos を呼び出します。
更新関数の書き方
状態を更新する方法には、大きく2つのパターンがあります。
更新後の値をどのように決めるかによって使い分けます。
1. 新しい値を直接渡す
setTodos([
{
id: "1",
text: "買い物",
completed: false,
},
]);
この書き方では、setTodos に新しい配列を渡して、todos を更新します。
更新後の値がすでに決まっている場合に適しています。
2. 現在の値をもとに更新する
setTodos((currentTodos) => [
...currentTodos,
newTodo,
]);
この書き方では、現在の状態を引数として受け取り、その値をもとに次の状態を返します。
上記の例では、既存のTODOリストを展開し、末尾に newTodo を追加しています。
前の状態をもとに次の状態を作る場合は、こちらの書き方を使うと安全です。
複数の状態更新がまとめて処理される場面でも、最新の状態をもとに更新できます。
Stateはどのように更新されるのか
const [todos, setTodos] = useState<Todo[]>([]);
todos は const で定義されています。そのため、「値を更新してよいのだろうか」と疑問に思いました。
しかし、実際には次のように todos を直接再代入しているわけではありません。
todos = ...
状態を変更するときは、必ず setTodos を呼び出します。
setTodos((currentTodos) => [
...currentTodos,
newTodo,
]);
setTodos を呼ぶと、Reactは次回のレンダーで使用するStateを更新します。その後、コンポーネント関数が再び実行されます。
再実行されたコンポーネント内では、useState がReact内部で保持している最新のStateを返すため、todos には更新後の値が入ります。
つまり、todos 自体を書き換えているのではなく、レンダーのたびに新しい todos が定義されているというイメージです。
Stateの値は、そのレンダー時点の「スナップショット」です。
setTodos を呼び出しても、現在実行中の処理にある todos の値がその場で変わるわけではありません。
次回のレンダーで、更新後の値を受け取ります。
最後に
useState を整理することで、Stateの更新は単純な変数の書き換えではなく、Reactによる再レンダーの仕組みとセットで成り立っていることを理解できました。
特に、以下の点を意識して使っていきたいです。
Stateは直接変更せず、更新関数を使う
前のStateをもとに更新する場合は、関数形式で書く
Stateの値は、レンダー時点のスナップショットとして扱う