Reactイベントハンドラ処理と再レンダリングなど
こんにちは。 Showcase Gigプラットフォームチーム所属のリョです。 開発中にあるバグが起きまして調べたらReactのhookとイベントハンドラについてもっと深く理解できました。
bugの説明
実際のコードをお見せできませんが、こちらの例と似てます。 先に全部のデータリストを取得して表示します。新しい値を入力して、「修正」ボタンをクリックしたらサーバ送信します。 この例では、操作してみると、意図せず古い値をサーバに送信してしまうようです。例えば:
1回目の修正なら、初期値である空文字列を送信します。
それ以降では、修正前の値になります。
結論
まとめて結論を言えば、イベントハンドラ内でsetSelectedhookが呼び出されていますが、新しい値はstateにすぐ反映されません。 よってmutateの中に使われている selected変数は最新のものではありません。最新の引数をmutateに渡せばバグを直せます。
const onSubmit = ({ id, name }) => {
// XXX: bug here
setSelected({ id, name });
mutate();
// ここを修正したら直ります。
// mutate({ id, name });
};
細かい説明
バッチ更新
Reactはイベントハンドラ内の処理を先に実行して、その後hookの値に変更があれば、新しい値を使って部品を再レンダリングします。 もっと深堀りしたら、複数のhook処理があるならまとめて実行した後再レンダリングします。例えば: onChange処理にはsetNamesとsetChangedTimes2つのhook処理があります。ある入力欄を変えたら1回再レンダリングが行われます。
const onChange = ({ id, name }) => {
const newNames = {
...names,
[id]: name
};
setNames(newNames);
setChangedTimes(prev => prev + 1);
};
上の画像通り処理は全部バッチされて、レンダリングは1回行われます。
バッチしない更新
React 17以前は、キーボードあるいはマウスイベントハンドラならhook処理をまとめて実行しますが、promiseから実行されている場合はまとめません。例えば:
useEffect(() => {
if (!inited) {
const fetchData = async () => {
setLoading(true);
// 略
setNames(newNames);
setData(data);
setInited(true);
setLoading(false);
}
fetchData();
}
}, [inited]);
上のコードなら、setXxxのhookは5つがあります、よって5回再レンダリングされて、プラス初期のレンダリングでトータル6回になります。
Reactの公式ドキュメントに記載されてないAPIを使えば強制的にまとめられるのです。例えば:
useEffect(() => {
if (!inited) {
const fetchData = async () => {
setLoading(true);
// 略
unstable_batchedUpdates(() => {
setNames(newNames);
setData(data);
setInited(true);
setLoading(false);
});
}
fetchData();
}
}, [inited]);
上の通りunstable_batchedUpdatesを使えば4つのsetXxxhook処理をまとめ実行できます。 初期レンダリング + setLoading + batch updatesでトータル3回になります。
(react-queryは裏でunstable_batchedUpdatesを使っているonSuccessをラップしている可能性が高いです)
パフォーマンスチューニング
再レンダリングの数を減らすのはパフォーマンスチューニングによく使われるやり方ですので、 個人的には仕事にもよく使いました。ですが、もともとReactは早いので逆に効果はそこまで著しくなかったです。 再レンダリングの数を減らすより重いレンダリングをチューニングした方が良さそうです。
まれに、再レンダリングを減らさないといけないユースケースがあります。例えば:styleの変更によるアニメーション。 このケースならほぼrefを利用して実現しています。よって、もともとのdelcarativeのやり方は一気にimperative になります。
これからのbatch update
上記はReact 17までの挙動です。18からはすべての更新はbatch updateになります。ここ バッチ更新はReactにとって重要です。ベントハンドラ中のhookを一部実行するとデータは不整合になる可能性があるんです。
最後
React経験はまだまだ浅いですが、今後もブログを書きながらもっと理解を深めていきます!
Showcase Gigで急成長するプロダクトを一緒に作る仲間を募集しています!
この記事が気に入ったらサポートをしてみませんか?