🙌

最新版のReactドキュメントを読んで、useStateの挙動について理解を深める

2023/04/28に公開

はじめに

以前はbeta版となっていましたが、
現在:4/27時点では、正式版となっているReactの公式ドキュメントが素晴らしい。

  • 具体的なコード例が含まれている
  • 以前のドキュメントはクラスコンポーネントでのコード例だったが、関数コンポーネントに変更している

今回は下記を参考にしています。
https://react.dev/learn/queueing-a-series-of-state-updates

お時間ある方は、ぜひドキュメントを読んでください(笑)
記事の趣旨としては、時間のない方向けに、抜粋した内容を日本語訳で紹介していきます。

※完全な日本語訳ではなく、自身の解釈も含まれていますので、ご理解いただければ幸いです。

よくある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更新関数と今回の記事では呼びます。
(直訳しているので違和感はありますが、上記のような関数のことです。)

  1. Reactは、イベントハンドラ内の他のすべてのコードが実行された後に処理されるように、この関数をキューに入れます。
  2. 次のレンダリングで、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セクションは自身でリファクタリングする内容なので、より理解が深まるはずです。)
https://react.dev/learn/queueing-a-series-of-state-updates#what-happens-if-you-update-state-after-replacing-it

記事のまとめ

  • 状態を設定(setState)しても、既存のレンダリングの変数は変化せず、新しいレンダリングを要求する。
  • Reactは、イベントハンドラの実行が終わった後に状態の更新を処理する。これをバッチ処理と呼ぶ。
  • 1回のイベントで複数回の状態更新を行うには、setNumber(n => n + 1)のように、更新関数を使用する。

個人的な感想

記事を通して、useStateでの再レンダリングの流れや、更新関数の内部での挙動について理解が深まりました!

下記の記事も分かりやすかったので、勝手ながら一部抜粋させていただきます。

更新後のステートが更新前のステートに依存しているなら、 setState には値ではなく関数を渡してあげましょう。

https://zenn.dev/stin/articles/use-appropriate-api

エックスポイントワン技術ブログ

Discussion