最新版のReactドキュメントを読んで、useStateの挙動について理解を深める
はじめに
以前はbeta版となっていましたが、
現在:4/27時点では、正式版となっているReactの公式ドキュメントが素晴らしい。
- 具体的なコード例が含まれている
- 以前のドキュメントはクラスコンポーネントでのコード例だったが、関数コンポーネントに変更している
今回は下記を参考にしています。
お時間ある方は、ぜひドキュメントを読んでください(笑)
記事の趣旨としては、時間のない方向けに、抜粋した内容を日本語訳で紹介していきます。
※完全な日本語訳ではなく、自身の解釈も含まれていますので、ご理解いただければ幸いです。
よくあるuseStateでの勘違い
下記のコードの場合setNumber(number+1)
を3回呼び出すので、「+3」ボタンをクリックすると、カウンターが3回インクリメントされると思ったかもしれません。
import React, { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}
しかし、各レンダーの状態の値(number)
は固定されているので、
最初のレンダーのイベントハンドラ内のnumberの値は、何度setNumber(1)
を呼び出しても、常にdefaultで指定している0
に対して1
をプラスする挙動になります。
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
この挙動には、もう1つの要因があります。
Reactは、イベントハンドラ内のすべてのコードが実行されるまで、状態の更新を処理するのを待ちます。その為、再レンダリングはsetNumber()
をすべて呼び出した後にしか行われないのです。
これにより、複数のコンポーネントから複数の状態変数を更新しても、再レンダリングの数を抑えることができます。
しかし、これはイベントハンドラやその中のコードが完了するまで、UIが更新されないことも意味します。この動作はバッチ処理とも呼ばれ、Reactアプリをより高速に動作させることに役立っています。
state
を複数回更新する方法
次のレンダリングまでに同じ次のレンダリングまでに同じ状態変数を複数回更新したい場合、setNumber(number + 1)
のように次の状態値を渡すのではなく、 setNumber(n => n + 1)
のようにキュー内の前の状態から次の状態を計算する関数を渡せば良いです。
ただ値
を置き換えるのではなく、Reactに「状態値に対して何かをする」ことを関数
を渡すことで指示する方法です。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
</>
)
}
上記のn => n + 1
を更新関数
と今回の記事では呼びます。
(直訳しているので違和感はありますが、上記のような関数のことです。)
- Reactは、イベントハンドラ内の他のすべてのコードが実行された後に処理されるように、この関数をキューに入れます。
- 次のレンダリングで、Reactはキューを通過し、最終的に更新された状態を提供します。
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
イベントハンドラを実行した際に、これらのコードを通してReactがどのように動作するかを説明していきます。
Reactは、前の更新関数
の戻り値を受け取り、それをn
として次の更新関数
に渡すといった流れです。
キューに入れられた更新関数
|
n |
返り値 |
---|---|---|
n => n + 1 |
0 |
0 + 1 = 1 |
n => n + 1 |
1 |
1 + 1 = 2 |
n => n + 1 |
2 |
2 + 1 = 3 |
上記の処理をキューで行いReactは3
を最終的な結果として保存し、useStateから返します。
その結果、上記の例で「+3」をクリックすると、値が正しく3つ増加します🎉
今回の記事の大枠の内容としては以上です。
更新関数
を使った他の例も、公式ドキュメントで紹介されているので、興味のある方は読んでいただければ幸いです。
(最下部のChallenge
セクションは自身でリファクタリングする内容なので、より理解が深まるはずです。)
記事のまとめ
- 状態を設定(
setState
)しても、既存のレンダリングの変数は変化せず、新しいレンダリングを要求する。 - Reactは、イベントハンドラの実行が終わった後に状態の更新を処理する。これをバッチ処理と呼ぶ。
- 1回のイベントで複数回の状態更新を行うには、
setNumber(n => n + 1)
のように、更新関数
を使用する。
個人的な感想
記事を通して、useStateでの再レンダリングの流れや、更新関数
の内部での挙動について理解が深まりました!
下記の記事も分かりやすかったので、勝手ながら一部抜粋させていただきます。
更新後のステートが更新前のステートに依存しているなら、
setState
には値ではなく関数を渡してあげましょう。
Discussion