見出し画像

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で急成長するプロダクトを一緒に作る仲間を募集しています!

この記事が気に入ったらサポートをしてみませんか?