Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

【考察】use APIの内部構造を予想してみた

※この記事はuse APIへの自分の理解と、実際にコードを起こして実験した結果を記しただけの 考察記事 です。正しさは保証されていません。「多分こういうマインドセットを持ってるとええんやな〜」くらいの温度感でお楽しみください。

導入: Throw a Promiseテクと use API はどういう関係なのか?

react 19から導入されたuse APIによって、reactは自然にPromiseを扱えるようになりました。 しかし、react18まではPromiseをthrowすることでレンダリングを中断するやり方も提供されていました。2つはユースケースがとても似通っていて混乱します。

このユースケースが似た2つ(use APIとThrow a Promise)の関係は、uhyo さんが記事でわかりやすくまとめています。

ちなみに、Suspenseを前提とする以上、useの中身は良く知られた「Promiseをthrowする」実装になっています。ただし、useは生でPromiseをthrowするのではなく、Suspense ExceptionというErrorオブジェクトでラップしてthrowするようです。これは開発者をなるべく混乱させないための配慮でしょう。

use API|React 19の新機能まるわかり

つまり、Throw a Promise を使った洗練された API が use API とのことです。また、「"Throw a Promise"をdeprecatedにしてuse APIを推奨していこう」というissueも立てられています (当該issue)。このことからも、今後は開発者が Throw a Promise を自らする必要はなくなっていくと思われます。

でも、use API が内部で Promise を throw しているとはどういうことなんでしょうか? 実際に書けるんでしょうか? そう思ってトライしてみたのが本記事になります。

use API を自作してみる

use API の要件としては以下になるはずです。

  • Promiseを受け取る
  • Promiseがresolveしたらその値を返す
  • PromiseがまだresolveされてなかったらPromiseをthrowする

これを非async関数で実装するには、resolveされた値を保存する記憶領域を外部に確保する必要があります。幸いにもJSにはWeakMapというものがあるので、それを使いましょう。

const dataMap = new WeakMap(); // resolveされた値を保存しておく場所
const use = (promise) => {
  promise.then((data) => {
    dataMap.set(promise, data); // resolveされたら保存する
  });
  const data = dataMap.get(promise);
  if (!data) throw promise; // dataがまだない=resolveされてなかったらpromiseをthrowする
  return data; // dataがある=resolve済みの場合はdataを返す
};

これを使って実際に実装してみたコードがこちらになります。

ちゃんとreact19のuse APIと同じような動きが実装できています。

(uhyoさんがいってる「Suspense Exceptionでラップしてる」の部分はよくわかりませんでした。有識者求ム)

まとめ: 今後は use API だけ使えば良さそう

ここまでの実験によって、throw a Promiseによってレンダリングの中断・再開を実装するテクニックは、use API によってより洗練されたインターフェイスとなったことが分かりました。

use API で大体のユースケースはカバーできると思うので、今後は基本的に use API だけ使えば良さそうです。