🎊Reactの2種類の新フック「useTransition」と「useDeferredValue」を最速で理解する（プレビュー版）

10月25日、Reactの新機能であるConcurrent Modeのプレビュー版が発表されました。この中には、Concurrent Modeの恩恵を得るために必要となる新しいAPIが含まれています。

これらのAPIの中心となるのが、Suspenseと2種類の新しいフック`useTransition`と`useDeferredValue`です。この記事ではこの2種類のフックに焦点を当てて、これらが何をしてくれるのか、どのようにこれらが新しいのかを解説します。

要するに、[Reactの公式ドキュメント](https://reactjs.org/docs/concurrent-mode-intro.html)を読んでまとめましたということです。特に、ガイドを一通り読んで理解しないとこれらのフックが何をしているのか理解しにくいため、最速で理解できるように要点をまとめ直しました。

なお、当該ドキュメントの最初にでかでかと書いてあるように、これらのAPIはまだプレビュー版であり、この記事に書いてあることからまったく別物に変化する可能性すらあります。ただ、少なくともこれらのAPIの基となる思想については今のうちに理解しておいて損はないでしょう。

以下ではいくつか例が出てきますが、これらの例は以下のリポジトリで公開しています。`npm start`でpercelのサーバーが立ち上がるので適当に試してみてください。

- https://github.com/uhyo/react-concurrent-sandbox

※ **追記**：当初「React 16.10」と記事タイトルに入っていましたが間違いだったので消しました。concurremt modeがReactのどのバージョンで正式リリースされるかは未定です。

## 3行でまとめると

最速といってもそこそこ長くなってしまったので3行まとめを用意しました。

- Reactがレンダリングを中断したり並行実行できるようになるよ
- `useTransition`は**今の画面を残しつつ次の画面を裏でレンダリング**したいときに使えるよ
- `useDeferredValue`は値の遷移を**いい感じに遅延**してほしいときに使えるよ

## Suspenseの基礎

さて、では解説に入っていきます。

ただ、本題に入る前にSuspenseの基礎について理解していただく必要があります。今回解説するフック（特に`useTransition`）がSuspenseに深く関連するものである以上これは仕方がありません。もう知ってるよという方は本題のフックを最速で理解するために次の節まで飛ばしましょう。

以下で解説する内容も今回のプレビュー版ではじめて利用可能になったものですが、コンセプト自体は前々から公開されていましたので知っている方もいるかと思います。

`Suspense`の最大の特徴は**コンポーネントが`Promise`を投げる**ということです。

### Promiseを投げるコンポーネント

では、最初の例を見ましょう。上記のリポジトリを動かしている方は、「01 Example of Suspense」という部分です。

この例は読み込みに時間がかかるコンテンツを表示するという状況を想定した単純な例です。「追加コンテンツを表示」と書かれたボタンが表示されており、押すと「Loading...」という表示に切り替わります。さらに2秒後、「外部から読み込んだ何らかのデータ」という文字列が表示されます。

コードを前半と後半に分けて示します。

```tsx:01-Suspense-example.tsx（前半）
export const Example01 = () => {
  const [showChild, setShowChild] = useState(false);

  return (
    <>
      <h1>01 Example of Suspense</h1>
      {showChild ? (
        <Suspense fallback={<p>loading...</p>}>
          <AdditionalContents />
        </Suspense>
      ) : (
        <button onClick={() => setShowChild(true)}>追加コンテンツを表示</button>
      )}
    </>
  );
};
```

これは何の変哲もないコンポーネントで、「追加コンテンツを表示」ボタンを押すと`showChild`というステートが`true`に切り替わって`<AdditionalContents />`が表示されます。今回その周りを`Suspense`で囲んでいます。

この`AdditionalContents`は追加コンテンツを表示するコンポーネントなのですが、追加コンテンツはボタンを押してからダウンロードする必要があるということにしましょう。もちろんダウンロードには時間がかかります。このコンポーネントは以下のように作りました。

```tsx:01-Suspense-example.tsx（後半）
const AdditionalContents = () => {
  // データをロードしてそのデータを表示
  const data = getData();
  return <p>{data}</p>;
};

let loadedData: string | null = null;
// 取得したデータを返す関数
// （まだ取得していないときは取得しつつPromiseを投げる
const getData = () => {
  if (loadedData) {
    // 取得済みなので返す
    return loadedData;
  } else {
    throw loadData(0).then(data => {
      loadedData = data;
    });
  }
};
```

`AdditionalContents`コンポーネントは`getData`関数を呼び出してデータを取得します。返り値は文字列です。`getData`関数はすぐ下で定義されています。

今回は手抜きの実装なので`loadedData`というグローバル変数があり、データのロードが完了した場合はここにデータが入っています。`getData`はこの`loadedData`が存在していればそれを直接返します。

データがまだロードされていない場合が問題です。この場合は`loadData`関数を呼び出してデータを読み込みます。`loadData`は`Promise<string>`を返すので、データが来たらそれを`loadedData`に代入します。

ここまでは良いのですが、何か`throw`とかいう構文が見えますね。実はこれは**Promiseをthrowしています**。というのも、`loadedData(0).then(...)`というのは`Promise`を返すからです。

この**Promiseをthrowする**というのが`Suspense`最大の特徴です。コンポーネントのレンダリング中に**Promiseがthrow**された場合は、**レンダリングがサスペンドされた**（今はまだレンダリングできない）という扱いになります。なぜPromiseをthrowするのかといえば、「**このPromiseが解決されたらもう一回試してくれ〜:pray:**」という意思表示のためです。

今回の場合、`getData`はまだデータが読みこまれていない場合、「データの読み込みが完了したら解決される`Promise`」を投げます。これにより、データが読みこまれたらもう一度コンポーネントがレンダリングされることが期待されます。そのとき再度`getData`が呼ばれますが、その場合はすでにデータがあるため再度Promiseをthrowする必要はありません。

### `Suspense`によるフォールバック

さて、コンポーネントをレンダリングしようとしたらPromiseを投げられてしまいました。これではレンダリング結果が存在しないのでコンポーネントをレンダリングできません。

これに対応するのがまさに`Suspense`コンポーネントの役割です。コンポーネントのレンダリングがサスペンドした場合、これを囲む`Suspense`の中身全体がフォールバックUI（`Suspense`に`fallback={...}`で渡したもの）に切り替わります。

ボタンを押した瞬間に「Loading...」という表示が出たのはこのためです。ここでは、以下のことが起こっています。

1. ボタンを押したことで`setShowChild(true)`が実行され、`showChild`が`true`になる。
2. `Example01`が再レンダリングされ、それに伴って`<AdditionalContents />`がレンダリングされる。
3. `AdditionalContents`のレンダリングがサスペンドする（Promiseが投げられる）。
4. `Suspense`がこれに反応して`fallback`の内容がレンダリングされる。
5. 「Loading...」と表示される。

その後は、`loadData(0)`が2秒で解決されるようになっているので（具体的な実装はサンプルのリポジトリをご覧ください）2秒後に`<AdditionalContents />`が再びレンダリングされます。今回はサスペンドしないため、`Suspense`のフォールバックは消えて中身がちゃんとレンダリングされます。

以上が`Suspense`の基本機能ですが、読者の中には「こんなの【ここに好きな状態管理ライブラリを入れよう】があればできるでしょ😆Promiseをthrowするなんて意味不明なことする意味なくない？😅」と思った方もいるでしょう。何か`isLoading`みたいなステートを用意すれば同じことができますよね。

それに対する答えは、これから説明するフックを通じて明らかになります。

では、いよいよ本題に入りましょう。

## `useTransition`

`useTransition`フックは、冒頭で述べたように**今の画面を残しつつ次の画面を裏でレンダリングする**ことを可能にするフックです。今の画面を残すというのは、DOMが今の状態を維持するということです。今回用意した例はこちらです。

```tsx:02-useTransition-example.tsx（抜粋）
export const Example02 = () => {
  const [showChild, setShowChild] = useState(false);
  // ここでuseTransitionを使用
  const [startTransition, isPending] = useTransition({
    timeoutMs: 10000 // タイムアウトを10秒に設定
  });

  return (
    <>
      <h1>02 Example of useTransition</h1>
      <Suspense fallback={<p>loading...</p>}>
        {showChild ? (
          <AdditionalContents />
        ) : (
          <button
            onClick={() => {
              // setShowChildをstartTransitionで囲んだ
              startTransition(() => {
                setShowChild(true);
              });
            }}
          >
            追加コンテンツを表示
          </button>
        )}
      </Suspense>
    </>
  );
};
```

`AdditionalContents`の中身はさっきと同じなので省略しました。また、`Suspense`の位置を都合により変更しています（`AdditionalContents`の周りではなく条件分岐全体を囲むように。理由は後述）。

このサンプルは、先ほどとは少し異なる挙動をします。というのも、「追加コンテンツを表示」ボタンを押しても「Loading...」が表示されません。代わりに、2秒後まで「追加コンテンツを表示」ボタンが表示され続け、その後直接データが表示されます。

先ほどと同じような構成なのにこのような挙動の違いが生じるのは、もちろん`useTransition`のおかげです。

### `useTransition`の機能

`useTransition`は、`startTransition`関数を得るのが主要な機能です。上のコードを見るともうひとつ`isPending`というものも取得していますが、これはあとで解説します。

`startTransition`関数は、**コールバック関数でステートの更新をラップすることで効果を発揮します**。具体例はボタンの`onClick`ハンドラの内部で使用されています。

`startTransition`内部でステートが更新された場合、**そのステート更新によって引き起こされた再レンダリングがサスペンドした場合はフォールバックを表示する代わりに今の状態を表示し続けます**。

つまり、今回のサンプルの動作は以下のように説明できます。

1. ボタンを押したことで`setShowChild(true)`が実行され、`showChild`が`true`になる。
2. `Example02`が再レンダリングされ、それに伴って`<AdditionalContents />`がレンダリングされる。
3. `AdditionalContents`のレンダリングがサスペンドする（Promiseが投げられる）。
4. サスペンドが発生したため、`useTransition`の効果により今の状態（`setShowChild(true);`される前の状態）がDOMに維持される。

3までは前と同じですが、`useTransition`の効果により4で違うことが起こります。`setShowChild(true);`を実行したにも関わらず、その前の状態が表示され続けるのです。

そして、サスペンドしていた`AdditionalContents`がレンダリング完了した時点で新しい状態（`setShowChild(true);`後の状態）が表示されます。

ちなみに、`useTransition`には`timeoutMs`というオプションが渡されています（デフォルトは多分500ミリ秒くらいです）。これは、前の画面を表示し続ける限度時間です。この時間を超えてもコンポーネントがサスペンドされていた場合は、諦めてステート更新後の状態が表示されます（ただし、依然としてサスペンドしているコンポーネントがあるため従来通り`Suspense`によるフォールバックが表示されます）。

また、`Suspense`の位置を外側に移動した理由ですが、`showChild`の値に関わらず`Suspense`が存在するようにするためです。どうやらここまで説明した`useTransition`の機能は`Suspense`のマウント時には働かないようです。つまり、`useTransition`内でのステート更新によって`Suspense`が新規に設置された場合は従来通りフォールバックが表示される挙動となります。

恐らく、`Suspense`が存在しない場合の挙動を最適化するためにそういう仕様になっているのではないかと思います。いずれにせよ、`Suspense`をどこに置くのかというのはどのコンポーネントのサスペンドをどこで対応するのかということにも関わるため、これからのReactを使いこなすにあたって重要な視点となります。

以上が`startTransition`の挙動でした。

### `isPending`の使用

さて、`useTransition`フックはもうひとつ`isPending`という値を返しています。次はこれを解説します。

`isPending`は真偽値で、「次の画面のレンダリングがサスペンドしたのでそれを待機している」という状況において`true`となります。

いまいち何を言っているのか分からないと思うので例を用意しました。先ほどとの違いは一箇所だけで、ボタンに`disabled={isPending}`という属性を追加しました。

```tsx:03-useTransition-Example2.tsx（抜粋）
export const Example03 = () => {
  const [showChild, setShowChild] = useState(false);
  // ここでuseTransitionを使用
  const [startTransition, isPending] = useTransition({
    timeoutMs: 10000 // タイムアウトを10秒に設定
  });

  return (
    <>
      <h1>03 Example of useTransition 2</h1>
      <Suspense fallback={<p>loading...</p>}>
        {showChild ? (
          <AdditionalContents />
        ) : (
          <button
            // ↓ この行を追加した
            disabled={isPending}
            onClick={() => {
              startTransition(() => {
                setShowChild(true);
              });
            }}
          >
            追加コンテンツを表示
          </button>
        )}
      </Suspense>
    </>
  );
};
```

この例は、「追加コンテンツを表示」ボタンを押すと即座にそのボタンが無効化（`disabled`）されてもう一度押せなくなります。他の点は先ほどと同じで、2秒後にボタンが消えて`AdditionalContents`の中身が表示されます。

今回は`disabled`だけですが、`isPending`は結構便利です。例えば、次の画面のレンダリングがサスペンドしている間、前の画面にいい感じのローディング表示を出すこともできるでしょう。

### `useTransition`による同時レンダリング

ここで何が起こっているのかを図で表してみました。上の例には、下の図でいうA, B, Cという3つの状態が存在します。Aは`showChild`と`isPending`が両方とも`false`の状態で、Bは`isPending`が`true`になった状態、Cは`showChild`が`true`になった状態です。

![useTransitionの動作の図解.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233667/ad713a6b-19bb-c740-1b88-167deb4620d7.png)

初期状態はAであり、このコンポーネントをレンダリングするとDOMにはAの状態が表示されます。

ユーザーがボタンを押すと`startTransition`内でステートの更新が行われます。つまり、プログラムによってCへの状態遷移が指示されたことになります。

ところが、`useTransition`の働きにより、まずBがレンダリングされます。ポイントは、Cよりも先にBがレンダリングされるという点です。これは、なるべく早くユーザーにフィードバックを返すためという理由があります。

つまり、一般にCというのは全く新しい状態であり読み込み完了まで時間がかかるかもしれない一方で、Bというのは普通はAに対してユーザーへのフィードバックを加えた状態です（上の例だとボタンが`disabled`になるというフィードバックがありました）。UX的な観点からフィードバックは速いほうが望ましいため、Bが先にレンダリングされることになります。

よって、ボタンを押すとまずBがレンダリングされてDOMに反映されます。その後でCのレンダリングが行われます。Cのレンダリングがサスペンドしなければ、Cのレンダリング結果がDOMに反映されます。Cがサスペンドした場合、DOMはBの状態を維持します。

Cがサスペンドしなかった場合はBをDOMに反映→CをDOMに反映という操作が連続して起きるのでBのレンダリングが無駄になってしまいますが、サスペンドした場合のUXを優先してこの挙動になっていると思われます。

同時レンダリングという割にはB→Cと直列にレンダリングが行われているように見えますが、Reactは内部的にBとCという2つの状態を同時に管理しているのであり、その意味では同時レンダリングと言えなくもありません。

また、Bの状態からさらにステートの更新（更新後をB'としましょう）を引き起こすという意地悪も可能です。この場合、Cのレンダリングがサスペンドしている状態でB'のレンダリングが行われることになり、同時レンダリング感が増します。さらに、この更新はC側にも反映されてC'が作られます。このときの挙動はcherry-pickのような感じです。

### `useTransition`の意義

ここまで説明したように、`useTransition`は`Suspense`と組み合わせることにより、画面の更新時にレンダリングがサスペンドする場合でのユーザーエクスペリエンスを向上させることができます。

ただ、似たような挙動は従来からあるステート管理の手法でも再現できるでしょう。ボタンを押した段階で`isPending`というステートを`true`にして、ロードが完了した段階で`showChild`を`true`にするようなロジックを書けばいいのです。

従来の手法の問題は、このようなロジックは**中央集権的になりがち**という点です。複数の画面にまたがるロジックである以上、ロジックを個々のコンポーネント内にしまい込んでしまうことは難しく、これは仕方がありません。逆に言えば、だからこそ、この問題をReact本体の追加機能として提供する価値があるとも言えます。

実際、`useTransition`（そして`Suspense`）の意義は**コンポーネント間のロジックの分離**にあるように思えます。`Suspense`を使う場合、「どんなデータをロードするか」とか「データの読み込み完了がいつか」といったロジックはそのデータを使うコンポーネント（上の例では`AdditionalContents`）に内包されます。また、フィードバックを表示する側（`isPending`を使う側）も、何がどうサスペンドしているのかは気にする必要がありません。

ここでのポイントは、ローディングのロジック（サスペンドするかどうかも含む）が「次の画面」に内包されていることです。こうなると、ローディングの画面を表示すべきかどうかも実際に次の画面をレンダリングしてみないと分からないのです。しかし、従来のReactでは次の画面をレンダリングしたら元の画面が消えてしまうという問題がありました。これを解決するために前の画面を残しつつ次の画面をレンダリングするという機構が必要になっているのです。

Reactはこのような方法で、中央集権的なロジックを脱してデータローディングをはじめとするロジックをコンポーネントの中に押し込めることに成功しました。

実際は“中央”がReactの内部に移動しただけかもしれませんが、その方がReactが“うまくやる“ことができると判断されたからこそこの機能があるのでしょう。将来的にはパフォーマンス上の恩恵などがあるかもしれません。今触ってみた限りでは、（UXではなく単純な処理速度の観点からは）直接パフォーマンスの向上に繋がっている感じはしませんでしたが。

また、そもそも中央集権的なステート管理が良いのか悪いのか、というのも答えのある問題ではありません。`useTransition`はReactが示したひとつの答えであり、こういった領域に手を出してくるのは少しライブラリとしての厚みが増してきたなという感想を持たせられます。

## `useDeferredValue`

もうひとつのフックは`useDeferredValue`です。こちらは実は`Suspense`と直接関係があるわけではないのですが、ReactがConcurrent Modeを実装したことにより可能になった機能です。

### 最初の例

一言で言うと、`useDeferredValue`は**値の更新をいい感じに遅らせてくれるフック**です。例を見ましょう。

```tsx:04-useDeferredValue-example.tsx
export const Example04 = () => {
  const [text, setText] = useState("");

  const deferredText = useDeferredValue(text, { timeoutMs: 10000 });

  console.log(text, deferredText);

  return (
    <>
      <h1>04 Example of useDeferredValue</h1>
      <p>
        <input value={text} onChange={e => setText(e.currentTarget.value)} />
      </p>
      <p>{deferredText}</p>
    </>
  );
};
```

これは、`useState`で宣言した`text`というステートを`input`要素を通じて変更できるというごく普通のUIです。

4行目で`useDeferredValue`に`text`を渡して、返り値として`deferredText`を得ています。さらにこの`deferredText`をレンダリングしています。

実際にやってみると、`input`に入力した内容が下に即座に反映されるように見えます。つまり、一見すると`deferredText`は`text`と同じになっているように見えます。しかし、実際に起こっていることは少し異なります。それをこれから解説していきます。

### `useDeferredValue`の機能

最初に述べたように、`useDeferredValue`は引数に渡した値を**いい感じに遅延**して返してくれるフックであると言えます。

この挙動は`text`が変わった瞬間に効果を発揮します。`text`が変わると当然再レンダリングが発生しますが、`deferredText`には変わる前の`text`が入りますが。

その後**いい感じのタイミング**でもう一度再レンダリングが発生し、その際`deferredText`に入るのは現在の`text`となります。

**いい感じのタイミング**というのは結局何かというと、Reactのスケジューリングに任せることになります。Reactが忙しくないときは一瞬で反映されますが、他の作業で忙しいときは結構反映が遅延されることになります。遅延の最大値を定めたい場合は

つまり、`useDeferredValue`の使い道は**一部のコンポーネントのレンダリングをいい感じに遅延する**という場合にあることになります。

実際、上の例では`console.log`で`text`と`deferredText`を並べて表示しているため、これを確かめてみましょう。入力欄に「abcde」と入力すると以下のようなログが並びます。左が`text`で右が`deferredText`です。

```
a 
a 
a a
a a
a a
a a
ab a
ab a
ab ab
ab ab
ab ab
ab ab
abc ab
abc ab
abc abc
abc abc
abc abc
abc abc
abcd abc
abcd abc
abcd abcd
abcd abcd
abcd abcd
abcd abcd
abcde abcd
abcde abcd
abcde abcde
abcde abcde
abcde abcde
abcde abcde
```

同じ状態が何回もレンダリングされているのは（多分strict modeみたいなものなので）気にしないことにして、よく見ると`text`が更新されるタイミングと`deferredText`が更新されるタイミングが異なっています。例えば`ab a`というログは、`text`が更新されて`'ab'`になったが`deferredText`はまだ前の状態である`'a'`である状態があったことを表しています。実際はその直後に`deferredText`も更新されて`'ab'`になるため、`ab ab`というログが出ることになります。

このように、上の例でも実際にまず`text`が更新されてからそれより遅れて`deferredText`が更新されていることが見て取れます。ただ、一瞬だったので目で見ても分かりませんでした。

### `useDeferred`による遅延を観察する

では、次の例では実際に遅延を観察してみましょう。そのためには、Reactに重い仕事をさせればいいことになります。とりあえずレンダリング量を増やしてみます。

```tsx:05-useDeferredValue-example2.tsx
export const Example05 = () => {
  const [text, setText] = useState("");

  const deferredText = useDeferredValue(text, { timeoutMs: 10000 });

  console.log(text, deferredText);

  return (
    <>
      <h1>05 Example of useDeferredValue 2</h1>
      <p>
        <input value={text} onChange={e => setText(e.currentTarget.value)} />
      </p>
      <Show10000Times text={deferredText} />
    </>
  );
};

const Show10000Times: React.FC<{
  text: string;
}> = memo(({ text }) => (
  <p>
    {Array.from({ length: 100 }).map((_, i) => (
      <Show100Times text={text} />
    ))}
  </p>
));

const Show100Times: React.FC<{
  text: string;
}> = ({ text }) => (
  <>
    {Array.from({ length: 100 }).map((_, i) => (
      <span key={i}>{text}</span>
    ))}
  </>
);
```

新たに追加された`Show10000Times`というコンポーネントは、渡された`text`を一万回描画するコンポーネントです。その中身は`Show100Times`が100個という構成です。実はこれは単に一万回ループしてspanを並べるよりもReactに都合が良い設定になっています（レンダリング処理を分割しやすいため）。

先ほどの例と同様にinput要素にテキストを入力してみると、下にすごく長い文字列が出現します。1万個の`span`要素をレンダリングしなければならない関係でどうしても重くなりますね。

今回も`console.log`があるので観察してみましょう。すると、先ほどとは違って`deferredText`は`text`よりもだいぶ遅れて付いてくることが分かります。Reactがレンダリングで忙しいため`deferredText`の更新が飛ばされるのです。

### `useDeferredValue`の意義

`useDeferredValue`で値の変化の遅延を表現できることが分かりました。変化の遅延は**いい感じ**に、もとい**暇ができたら値が変化する**という感じの挙動になります（正確な挙動を調べたわけではないのでありませんが）。

では、これは何の役に立つのでしょうか。その答えは、「**画面の更新の一部を遅延したいとき**」です。画面を更新するときに最優先で表示したい情報とそうでもない情報があるような場合に`useDeferredValue`を使用できる可能性があります。

最優先で表示したい情報というのは、代表的なものは**ユーザーへのフィードバック**です。これは`useTransition`のときも出てきたキーワードですね。特に上のサンプルの`input`要素の場合、これはcontrolled componentであるため`text`というステートの変更がすぐに反映されないとユーザーが違和感を感じてしまうため、何を差し置いても最優先で再レンダリングを完遂する必要があります。

一方で、下の`<Show10000Times text={deferredText} />`はそれよりも優先度が低い情報であり、input要素と同レベルの軽快さは求められません。そのためここで`text`ではなく`deferredText`を渡しています。これにより、`text`が更新された瞬間に`Show10000Times`を再レンダリングする必要が無くなるのです。

実際にサンプルを動かしている方は、試しにこれを`<Show10000Times text={text} />`に変えてみましょう。すると、input要素の応答性がかなり悪くなるはずです。これは`text`が変わるたびにこのコンポーネントを再レンダリングする必要があり、それが終わらないと`input`要素も再レンダリングされないからです。

まとめると、この例では`useDeferredText`を用いて`Show10000Times`の再レンダリングを遅延させることで、`input`要素の反応速度を挙げているのです。

`useTransition`のときと同様に、この問題も適切なステート管理によっても解決できるかもしれません。そんれにも関わらずReactがこの機能を用意した理由は、やはり**レンダリングの一番適切なスケジューリングが出来るのはReact自身だから**ということに尽きるでしょう。

### `useDeferredValue`が得意な状況と苦手な状況

実は、上の例はサンプルとして最善のものではありません。実際、`input`要素をいじっているとそこそこ頻繁に（`Show10000Times`の再レンダリングが走るたびに）応答性が悪くなってしまいます。

これは、（まったく効果が無いわけではありませんが）今回のサンプルが`useDeferredValue`の苦手な状況だからです。

具体的には、今回応答性が悪くなる原因は**`show10000Times`のレンダリングのボトルネックがDOMへの反映だから**です。ユーザーに中途半端な状態を見せないために、いわゆる仮想DOMを実際のDOMへと反映させる作業は一気に行う必要があります。そのため、せっかくReactがレンダリングを中断できる機能を実装したといっても、DOMへの反映は分割できないのです。まあ、だからこそ実際のDOM操作を最小限にすることが重要になってくるのですが。

逆に言えば、仮想DOMの構築段階が重い場合（単純にツリーが大きいとか、関数コンポーネントに重い処理があるなど）の場合は`useDeferredValue`の得意な状況となります。仮想DOMレベルのレンダリングの段階ではReactがより上手にスケジューリングを行うことができるからです[^one_function_component]。

[^one_function_component]: ただし、ひとつの関数コンポーネントの処理にものすごく時間がかかるというような状況には無力です。Reactでもさすがに関数の実行を中断させることはできないからです。それよりも、たくさんの関数コンポーネントがあって全部処理すると重いというような状況に対してより有効です。

## `useDeferredValue`と`useTransition`の併用

ここまでで2つのフックを紹介しましたので、最後に2つを併用する例を紹介します。

特に、上の`useDeferredValue`の例はメインスレッドを専有するレンダリング処理に対する対応策でしたが、実は`useDeferredValue`は`Suspense`と組み合わせてもいい感じに動きます。

今回の例はこれです。

```tsx:06-useDeferredValue-example3.tsx（抜粋）
export const Example06 = () => {
  const [text, setText] = useState("");
  const [dataId, setDataId] = useState(0);
  const [startTransition] = useTransition({ timeoutMs: 10000 });

  const deferredDataId = useDeferredValue(dataId, { timeoutMs: 10000 });

  return (
    <>
      <h1>06 Example of useDeferredValue 3</h1>
      <p>
        <input
          value={text}
          onChange={e => {
            const newText = e.currentTarget.value;
            setText(newText);
            startTransition(() => {
              setDataId(newText.length);
            });
          }}
        />
      </p>
      <Suspense fallback={<p>Loading...</p>}>
        <p>データID: {dataId}</p>
        <LoadedContents dataId={deferredDataId} />
      </Suspense>
    </>
  );
};

const LoadedContents: React.FC<{ dataId: number }> = ({ dataId }) => {
  const data = getData(dataId);
  return <p>{data}</p>;
};
```

また`Suspense`の話なので、データをロードして表示するコンポーネント`LoadedContents`を用意しました。データを読み込むには時間がかかるため、このコンポーネントはサスペンドする可能性があります。

実際の動作としては、`input`要素に入力した文字列の文字数がデータIDとして表示され、さらに一定時間後にそのIDのデータが読みこまれて表示されます。`useDeferredValue`の効果により、データIDと実際に表示されるデータに差異が発生することがあります。下の例では、データIDが21なのに実際に表示されているデータは17番のものです。

![例6のスクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233667/70eab0e5-4439-29e9-b658-c8c686cdc5a2.png)

### `useDeferredValue`と`useTransition`の併用時の挙動

中身の解説に移ります。メインのコンポーネント（`Example06`）のステートは二つで、ひとつは`useDeferredValue`の例と同じく`text`、もうひとつは`dataId`です。`dataId`は読み込みたいデータの番号です。`dataId`は`text`の文字数とします。

それならわざわざ`dataId`というステートを用意しなくても`const dataId = text.length;`でいいような気がしないでもないですが、今回そうできない理由があります。`input`要素の`onChange`属性を見ると分かりますが、`text`は`startTransition`の外で、`dataId`は中で変更しています。この違いのためにステートを別々にする必要があったのです。

`startTransition`の外で変更されている`text`は即座に変更されます。これにより、`input`要素は常にユーザーの入力に対するフィードバックを素早く返します。

`dataId`は`<p>データID: {dataId}</p>`というところで使われています。これは`startTransition`の中で変更されるため、新しいデータが読みこまれるまでは表示が更新されません……といいたいところですが、実はそうでもありません。上記のように、新しいデータが読みこまれる前に`dataId`が更新されることがあります。

これは`useDeferredValue`によるものです。`LoadedContents`に渡しているのは`deferredDataId`であり、`dataId`が更新された直後はまだ`deferredDataId`が更新されていないためにサスペンドが発生しません。これにより、即座に`dataId`の更新が画面に反映されるのです。ただ、`useTransition`の効果により、一旦サスペンドが発生したら`dataId`の更新が画面に反映されなくなります。これにより、`dataId`が常にぴったり`text`に追随するというわけでもなくなります。

ここで強調したいのは`useDeferredValue`の効果です。サスペンドするコンポーネントに渡される値を遅延させることで変更直後のサスペンドを防ぐことができる可能性があり、その結果として`dataId`の変化が早めにユーザーに表示されるのです。

今回はあまり意味がない例に見えますが、複数のソースからデータを読み込むときに早いほうのデータを読み込めたらすぐに表示し、かつ遅いデータは読み込め次第表示する（それまでは古いデータを表示しておく）というような場合において役に立ちそうです。

### `useDeferredValue`と`Suspense`

今回の例でも、`useDeferredValue`によるいくらかの遅延が発生していることが分かりました。

しかし、今回は前の例のようにメインスレッド的に重い処理があるわけではありません。代わりにあるのはサスペンドするコンポーネントです。

このことから、サスペンドするコンポーネントも`useDeferredValue`に対するスケジューリングに影響を与えることが分かります。

正直に述べると、どんな時にどれくらい遅延するのかはよく分かりませんでした。例えば公式のサンプルではサスペンドしていたコンポーネントが一つレンダリング完了した時点で遅延更新が行われる挙動が確認できますが、なぜそのタイミングなのかは非自明です。恐らく、React側としてもここはヒューリスティクスの入る余地がある部分なので、あまり厳しい制約をかけたくないのであろうと思います。

今のところ唯一信頼できる性質は、**元の値が変化した瞬間は`useDeferredValue`の返り値はそれに追随しない**というものだと思われます。つまり、必ず元の値（`useDeferredValue`の引数に渡された値）よりも古い値が返される瞬間が存在するようです。

個人的には、正式版リリースまでにはもう少し分かりやすくなってほしいなあと思わないでもありません。使い続けていればそのうち理解できるかもしれませんので、次の記事を期待せずにお待ちください。

## 所感

まず最初に`Promise`をthrowすることについて述べておくと、なかなか面白いですね。非同期処理というのはどこか一箇所でも混ざりこむと処理の全体を非同期に対応するように書かないといけないわけですが、その対応をReactに丸投げ（文字通り）することで強引に解決し、ユーザーランドではまるで同期処理のように見える処理を書くことができます（関数型言語のユーザーたちは副作用の種類が変わっただけじゃんと言うかもしれませんが）。それが“良い”かどうかはさておき、React的な発想であると感じられました。

さて、この記事では2つの新しいフックを紹介しましたが、両方に共通するキーワードは**ユーザーエクスペリエンス**でしたね。特に、望ましいフィードバックを最速で返すことに焦点が置かれています。もちろんそれは従来から我々が心血を注いできたものですが、今回React本体にそれを支援する機能が入ったことになります。

その背景は、一つはReact本体がやるべき仕事をReact本体がやるようにしたという単純な話です。しかしもう一つ、中央集権的ではなくコンポーネントベースな手法をさらに推進していく意思が感じられます。

個人的な所感としては、確かにその方向性で出来ることが広がったものの、まだ全部把握しきれる気がしないなあと感じています。色々なコンポーネントがステートを持っているだけでも大変なのにそれらがサスペンドしたりしなかったりするとなると、ロジックの全体像の把握は非常に困難になります。

Reactの思想としては、コンポーネントが成す木構造を中心に据えるという点ではとても一貫しています。木構造の上でロジックを適切に分割してやることで我々が考えなければいけないことを減らすという方向性が見て取れます。

ここから言えることは、`Suspense`を始めとしたこれらの新機能を使いこなすにはそのための精巧な設計が必要であろうということです。従来中央集権的な方法で解決されてきた問題に対してReactはコンポーネントベースの新たなアプローチを与えました。次に我々がすべきことはこのアプローチを受け入れ咀嚼することです。

React自体が「`Suspense`エコシステム」と呼んでいるように、`Promise`をthrowするといった概念は、周辺ライブラリに新たな影響を与えます。これはちょうどHooksが登場したときのようです。

Reactはdata fetching libraryを主に見ているようですが、その流れで状態管理ライブラリにも新しい設計のものが出るのではないかと思います。この記事を読んでconcurrent modeを完全に理解したという方がいれば自分で作ってみてもいいかもしれません。

また、今回の新機能によってReactの非自明さが増したという印象を受けました。特に`useDeferredValue`の挙動において顕著です。それに、`useTransition`が絡んでくると何がいつ再レンダリングされるのか正直まだ予測が付きません。使っているうちに理解できるのか、それとも理解しようとしないとずっと非自明なままなのかはまだ定かではありません。

先にも述べたようにReactは結果の整合性を保ちつつレンダリング中の種々の保証を減らす方向に動いています。自分はこれまでReactはフレームワークじゃなくてライブラリだよという主張を続けてきましたが、何となくフレームワークっぽさが出てきたなあという気がしないでもありません。

## まとめ

まず、記事の後半になるにつれて文章が読みにくくなっていったと思います。最初は真面目に記事を書いていたのですが後半疲れてきて、だんだんReactを触って脳内をダンプしただけみたいな文章になってしまいました。それでも何かの参考になれば幸いです。

ということで、10月25日に公開されたReact Concurrent Modeのプレビュー版を調査し、`useTransition`と`useDeferredValue`という2つの新しいフックについてまとめました。

`useTransition`は画面遷移前と後の2つの状態をReactに同時に管理してもらうことができるフックでした。また、`useDeferredValue`は値が**いい感じ**に遅延しながら変化するというフックでした。

これらがどんな問題を解決するのかと言えば、ユーザーエクスペリエンスの向上です。そのためにReactが供えているべき機能として上記のフックが追加されたわけです。

これが実現するには中断可能なレンダリングの機構が必要で、それが長い時間をかけて実装されたconcurrent modeです。

所感は上に述べた通りですが、まとめ直すとReactの非自明さが少し増したなという印象を受けました。筆者は7〜8時間くらいはReactのconcurrent modeをいじりましたが、まだ良くわかっていない点も多くあります。

理解を深めるにはもっと使い倒す必要があると感じていますが、React公式側からのさらなる解説もできれば期待したいところです。