【ReactHooks】useStateまとめ
目次
useStateとは?
ustStateとは、React本体に関数コンポーネント専用のデータ保存領域を作成させて、そのデータの読み書きができるReactフックです。
stateとはコンポーネントが内部で保持する「状態」のことを指しており、stateはpropsと違って後から変更できます。
useStateの動作について詳しく見ていくと、以下のような流れなります。
① useStateはReact内部と接続し状態を保持できるようにする。
② useStateは現在の値と、値を更新するための関数(更新関数)を返す。
③ useStateの更新関数に新しい値を渡すと、Reactに対してコンポーネントを再実行(再レンダリング)するように命令する。
useStateの使い方
useStateを利用するためには、まずuseStateをimportする必要があります。
import { useState } from 'react';
そして、コンポーネント内で状態管理を行いたい変数をuseStateを使って宣言します。
// 一般化した書き方
const [state, setState] = useState(initialState);
// 以下は説明のためにカウンターを作るという例で、countという変数を定義する場合
const [count, setCount] = useState(0);
このstateが状態管理を行いたい変数(ステートフルな値)で、setStateがそれを更新するための関数です。
useStateの第一引数で変数(state)の初期値を設定して、初回レンダリング時に返されるstateはこの初期値と等しい値になります。
setStateに新しいstateの値を設定して実行すると、その新しいstateの値を受け取りコンポーネントを再レンダリングするように命令(スケジューリング)します。
setCount(newState);
再レンダリング時には、stateの値は常に更新を適用した後の最新のstateとなります。
宣言するときの注意点として、必ず関数のトップレベルで宣言する(最初にすぐ宣言する)必要があります。
これはuseStateに限らず、他のHooksについても同様です。
またif文やfor文の中で使用することもいけません。
更新関数に現在のstateの変数を用いた値を渡す場合
更新関数の引数はプリミティブな値だけでなく関数を渡すことができて、カウンターを実装しようとした時など新しいstateが現在のstateに基づいて計算される場合(例えば、現在のcountに1を足した値にしたい場合)、setStateには以下のように関数を渡さないとバグの原因になります。
const [count, setCount] = useState(0);
setCount(prevCount => prevCount + 1); // countを使わない!
この関数は更新関数に対して、現在のstateを参考にしてそれに1足したものを返しています。
この「現在のstate」とはcount
のことではなくReact内部でstateを計算している途中の値のことを指します。
この関数は「現在React内部で計算中のstateの値」を引数(上記でいうprevCount
)として参照できるようになっており、この引数は自分で好きな名前をつけて使うことができます。
なぜ関数を渡さなければならないかというと、上記のように1度だけ更新関数が呼ばれるだけなら想定通りに動くかもしれないですが、
更新関数がすぐ連続して呼び出されたりした時、更新関数の呼び出しが非同期であり、stateが即座に更新されるわけではないため、関数を渡さないと想定通りの挙動をしないことが起こり得るからです。
const [count, setCount] = useState(0);
setCount(count + 1); // ①
setCount(count + 1); // ②
このように更新関数の引数に関数を渡さずcount
を使ってしまうと、以下のような挙動が起こり得てしまいます。
① count
の初期値は0だから1が更新される
② まだcount
が1に更新されるReact内部の処理が終わる前にこの行が実行されて、count
が初期値の0のまま更新関数に渡されて、また1が更新されてしまう
setCount(prevCount => prevCount + 1); // ①
setCount(prevCount => prevCount + 1); // ②
関数を渡すようにすると
① count
の初期値は0だからprevCountも0で、そのprevCountに対して1が足され、結果1が更新される
② prevCount
は「現在React内部で計算中のstateの値」であるから、①の処理で更新された1がじっくりと渡され、そのprevCount
に対して1が足され、結果ちゃんと2が更新される
よって新しいstateが現在のstateに基づいて計算される場合は、更新関数の引数に関数を渡すようにしてあげましょう!
配列やオブジェクトのstateの更新について
配列やオブジェクトのstateを更新しようとする時、setStateで新しい値に更新したのに更新されず再レンダリングされない!とつまづくことがあります。
公式ドキュメントにこのような記述があります。
state 更新の回避
現在値と同じ値で更新を行った場合、React は子のレンダーや副作用の実行を回避して処理を終了します(React は Object.is による比較アルゴリズムを使用します)。
これは新しい値に見せかけて実は同じ値で更新をした場合は再レンダリングされないということを言っています(この同じ値かどうかの判定はObject.is による比較アルゴリズムで判定している)。
どういうことかというと、
const [colors, setColors] = useState(["赤", "青", "黄"]);
//上記のようなuseStateの変数があったとして配列の最後に緑を追加する処理
colors.push("緑")
setColors(colors)
このような場合、緑を元の配列に追加しただけで中身は変わっているけれど、新しいstateの値とはみなされず、同じ値とみなされて再レンダリングされません。
再レンダリングされるようにするためには、全く新しい変数に元の配列のコピーを取って、それを変更などして更新関数に渡すとしっかり再レンダリングされます。
const newArray = [...colors];
newArray.push("緑");
setColors(newArray);
関連記事