React 公式ドキュメントを読んでいて state・useState についての理解が深まったので共有します
この記事はすべて React 公式ドキュメントの内容です。
わかりにくい部分があれば公式ドキュメントを読むことおすすめします。
この問題解けますか?
以下のチャレンジ問題が state の理解に役に立ちました
問題文(公式ドキュメントの引用)
あなたは、ユーザが美術品に対して複数の注文処理を同時並行で行える、アートマーケットアプリの開発をしています。ユーザが “Buy” ボタンを押すたびに、“Pending”(処理中)カウンタが 1 つずつ増えるようにする必要があります。3 秒後に “Pending” カウンタが 1 減り、“Completed” カウンタが 1 増える必要があります。
しかし、“Pending” カウンタは意図した通りに動作していません。“Buy” を押すと、“Pending” が -1 に減少します(あり得ない!)。また、2 回素早くクリックすると、両方のカウンタが予測不可能な挙動を示します。
なぜこれが起こるのでしょうか? 両方のカウンタを修正してください。
これがすぐわかる方は記事を読む必要はありません!!!
import { useState } from 'react';
export default function RequestTracker() {
const [pending, setPending] = useState(0);
const [completed, setCompleted] = useState(0);
async function handleClick() {
setPending(pending + 1);
await delay(3000);
setPending(pending - 1);
setCompleted(completed + 1);
}
return (
<>
<h3>
Pending: {pending}
</h3>
<h3>
Completed: {completed}
</h3>
<button onClick={handleClick}>
Buy
</button>
</>
);
}
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
回答
更新用関数を利用して、handleClick を修正する
修正後
import { useState } from 'react';
export default function RequestTracker() {
const [pending, setPending] = useState(0);
const [completed, setCompleted] = useState(0);
async function handleClick() {
setPending(p => p + 1);
await delay(3000);
setPending(p => p - 1);
setCompleted(c => c + 1);
}
return (
<>
<h3>
Pending: {pending}
</h3>
<h3>
Completed: {completed}
</h3>
<button onClick={handleClick}>
Buy
</button>
</>
);
}
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
これらがわかれば解ける(と思う)
stateが更新されてから、画面が更新されるまで
state が更新されてから画面に描画されるまでの流れはおおまかにこのようになっています。
- トリガ:初回、コンポーネントの state の更新
- レンダー:React がトリガのあったコンポーネントを呼び出す
- コミット:DOM を変更
- ブラウザの画面を再描画
まず、特定のコンポーネントの state が更新されます。
(例えばユーザーがボタンを押す(handleClick)など)
そして state の更新があったコンポーネントを React が検知して、その変更を読み取ります。
最後に、変更箇所を DOM に伝えてブラウザの画面が再描画されます。
state はスナップショットである
以下のコードではボタンをクリックすると、アラートは「0」と表示されます。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number);
}, 3000);
}}>+5</button>
</>
)
}
このことからアラートに渡された state は「スナップショット」だということが分かります。
number は次のレンダーで 5 に更新されます。
他にはイベントハンドラ内のすべてのコードが実行されるまで、React は state の更新処理を待機するといういうことも知っておくとよいです。
次のレンダー前に同じ state 変数を複数回更新する場合
更新用関数を セッター(setSomething)に渡す
例:setNumber(n => n + 1);
更新用関数をセッターに渡せば正解できますが、背景まで知っておくと腹落ちしやすいのかなと。
これからもReact に限らず公式ドキュメントを読む癖をつけて言語やフレームワークの理解を深めていきます。