状態管理を行うuseStateは、ReactでUIを開発する際、特によく使用するフックです。
useStateを使えるようになれば、以下のようなアプリも作れるようになります。
useStateで作ったタスク管理アプリ
本記事で、useStateの基本的な使い方をマスターしておきましょう。
1.useStateとは
useStateは、コンポーネント内で、「state」を管理するためのフックです。
useStateの例
const[value, setValue]=useState(initialValue);
「state」は、そのコンポーネントが持つメモリ(記憶)です。
また「state」は、普通の変数のように「関数が終わると消えてしまう」ようなものではありません。
変更されると再レンダリング(描画)されるようなデータを保持します。
つまりは、useStateを使うことで、「値を保持しつつ画面を再描画」「値の変更をトリガーに再描画」させられます。
2.useStateの基本構文
useStateの構文は以下の通りです。
useStateの構文例
const[value, setValue]=useState(initialValue);
useStateの各要素の意味
const[現在の状態, 状態を更新するset関数]=useState(初期値);
なお、実際のコーディングでは、慣習として
[~~~~(小文字から始まる英単語),~~~~set(大文字から始まる英単語)]
というふうにuseStateの返り値を命名します。
2-1.引数:初期値
以下の例文の「initialValue」に当たる要素が、useStateで管理するstateの初期値です。
const[value, setValue]=useState(initialValue);
stateの初期値には、どんな型の値でも設定できますが、初回レンダー後は、使われません。
また、以下のように、関数(初期化関数:initializer functionと呼びます。)を渡すこともできます。
関数を渡す場合、コンポーネントを初期化するときに初期化関数が呼び出され、その返り値を初期値として保持します。
const[value, setValue]=useState(createInitialValue());
なお、初期化関数は、
- 純粋関数である
- 引数を取らない
- いずれかの型の値を返す
である必要があります。
初期化関数の落とし穴
以下の初期化関数は、同じように動作するように見えます。
しかし実は、以下のように、動作が異なります。
例1(関数を呼び出した結果を渡しており、レンダー毎に関数が実行される)
const[value, setValue]=useState(createInitialValue());
例2(関数自体を渡しており、初回レンダー時にだけ関数が実行される)
const[value, setValue]=useState(createInitialValue);
例1では、初期化が重い処理だとパフォーマンスが悪くなるので注意が必要です。
2-2.返り値1:現在の状態
現在のstateが格納されています。
初回レンダー中は、初期値(上記ではinitialValueに当たる要素)と同じ値になります。
2-3.返り値2:状態を更新するset関数
以下の例文の「setValue」に当たる要素が、状態を更新するset関数です。
set関数を使うことで、stateの値を更新し、再描画を引き起こせます。
const[value, setValue]=useState(initialValue);
set関数は、以下のように使います。
setValue(nextValue);
引数であるnextValueは、次にstateに持たせたい値で、いずれの型の値でも入れられます。
set関数の使い方としては、
setValue(5);
のように、「次のstateを直接渡す方法」があります。
また、以下のように、nextValueに関数(更新用関数:updater functionと呼びます。)を渡すことも可能です。
setValue(prev=> prev +1);
ただし、更新用関数は、
- 純粋関数である
- 処理中のstateの値を唯一の引数とする
- 次のstateを返す
である必要があります。
なお、set関数の更新が実行されるのは、再描画が起きた後です。
そのため、set関数を呼び出した直後にstateを読み取っても、新しい値は表示されません。
また、現在のstateと新しいstateの値が同じ場合、描画がスキップされます。
3.useStateで配列やオブジェクトを扱う方法
useStateでは、以下のように、stateに配列やオブジェクトを持たせることが可能です。
const[items, setItems]=useState(["a","b"]);
const[user, setUser]=useState({name:"Tarou",age:50});
ただし、useStateで配列やオブジェクトを扱う場合、注意したい点があります。
stateに格納される配列やオブジェクトは、基本イミュータブル(immutabl:書き換え不能)です。
useStateに持たせた配列やオブジェクトを操作する場合は、元の配列やオブジェクトを直接操作するのではなく、新しい配列やオブジェクトを作成(コピー)して、そのデータをstateにセットする必要があります。
useStateで配列を扱う方法
useStateでオブジェクトを扱う方法
useStateでオブジェクト配列を扱う方法
4.実際にuseStateを使ってみよう(メモアプリ)
ここでは、以下のような「メモアプリ」を使って、useStateの使い方や仕組みを理解しましょう。
このメモアプリは、useStateを使って「追加・削除・編集」という基本的なCRUD操作を実装しました。
配列の追加にはスプレッド構文、削除にはfilter、更新(編集)にはmapを使うことで、React の「状態を直接変更せず、新しい配列を作る」という原則を学べます。
また、編集モードの切り替えには三項演算子を使っており、条件付きレンダリングによって、UIを切り替える仕組みも理解できます。
ソースコード
4-1.useStateで管理している状態
このメモアプリで管理している状態は以下の通りです。
const[text, setText]=useState('');//入力欄に入力している文字。
const[memos, setMemos]=useState([]);//メモの一覧。
const[editingIndex, setEditingIndex]=useState(null);//編集中のメモのindex。
const[editingText, setEditingText]=useState('');//編集用入力欄に入力している文字。
4つのuseStateで、以下の4つの機能を実装しています。
- メモの追加
- メモの削除
- 編集モードの切り替え
- 編集内容の保存と取り消し
4-2.メモの追加
以下は、メモアプリの「追加機能」部分だけ抜き出したものです。
上記のdeleteMemo関数では、
- trim()で未入力時のメモ登録の防止
- スプレッド構文を使って配列をsetMemosで追加
- メモ追加後に入力欄をクリア
といった内容を行っています。
4-3.メモの削除
以下は、メモアプリの「削除機能」部分だけ抜き出したものです。
上記のaddMemo関数では、filterを使って、削除したいindex以外を残した新しい配列を作って、setMemosで上書きしています。
4-4.編集モードの切り替え
以下は、メモアプリの「編集モードの切り替え」部分だけ抜き出したものです。
上記のstartEdit関数では、
- 編集対象の index を保存
- 編集用の入力欄に、元のメモ内容をセット
といった内容を行っています。
また、JSX部分では、
{editingIndex === index ?(<>編集UI</>):(<>通常時のUI</>)}
という三項演算子を使って、UIの切り替えを行っています。
三項演算子とは
三項演算子は、JSXでよく使われるJavaScriptの演算子で、以下のような構文で使用されます。
(条件式)?(条件式が真なら評価される):(条件式が偽なら評価される)
今回は、stateの値を条件として、2種類のHTMLを返す使い方をしました。
4-5.編集内容の保存と取り消し
以下は、メモアプリの「編集内容の保存と取り消し」部分だけ抜き出したものです。
上記のsaveEdit関数では、
- mapを使って、目的のindexのメモだけ書き換える
- 編集が終わったらeditingIndexをnullに戻す
- 編集用入力欄をクリアにする
といった内容を行っています。
editingIndexをnullに戻すことで、先ほど解説した三項演算子によって、編集UIから通常時のUIに切り替わります。
なお、上記のcancelEdit関数は、saveEdit関数の「mapを使って、目的のindexのメモだけ書き換える」という操作がないだけで、ほかは同じ動作を行います。
5.注意点
5-1.useStateの呼び出し位置
useStateは、「コンポーネントのトップレベル」か「カスタムフック内」でのみ使えます。
そのため、ループや条件文のなかに記述すると、エラーが発生するため注意が必要です。
5-2.イベントハンドラの記述方法
「set関数自体」あるいは「useStateを使った関数」を、間違った方法でイベントハンドラに渡すと、
Too many re-renders. React limits the number of renders to prevent an infinite loop.
というエラーが出ることがあります。
これは、レンダー中にset関数を呼び出し、コンポーネントが無限ループに入ると起こります。
イベントハンドラの正しい渡し方
5-3.useStateの更新タイミング
Reactにおいて、stateの更新は、バッチ処理で行われます。
つまり、useStateのset関数は、呼び出されたその場で値を書き換えるわけではありません。
set関数は、呼び出されると、処理をキュー(次の再描画でまとめて更新するための予約の列のようなもの)」に登録します。
そのため、set関数の直後にstateを読んでも再描画が起きていないため、まだ古い値が表示されます。
よって、useStateのバッチ処理について理解していないと、以下のような勘違いをする場合があります。
5-4.開発環境での振る舞い
開発環境(npm run devなど)でReactの動作を確認する場合、「初期化関数」「更新用関数」が2回呼び出されますが、これは正常な動作です。
本番環境では、2回呼び出されません。
6.あとがき
useStateは状態を管理するためのフックで、UIの変化は基本的にuseStateで制御します。
そのため、React開発において、useStateは非常に重要なフックです。
特に、useStateは別のフックと組み合わせて使うこともあるので、本記事で基本的な使い方をきちんとマスターしておきましょう。


