React 19 Betaがリリースされましたね! このブログでもReact19の新機能を解説して行こうと思います。
今回は、use Hookです。
公式ドキュメントには、以下のように書かれています。
use はプロミス (Promise) やコンテクストなどのリソースから値を読み取るための React フックです。
use というシンプルな名前、 何なんでしょうか?
Bing Image Creatorが生成した画像を使っています
use Hookとは
use Hookには大きく2つの目的で使われます。1つ目はコンテクスト(Context)の読み取りです。そのためには、以前からuseContext Hookがありましたが、今回のuse
も同じ目的で使えます。
ただしuseContext
はHooksの基本ルール通りに、関数コンポーネントのトップレベルで使う必要があります。if文の中では使えませんでした。
しかし今回のuse
は、if文の中でも使えます。これで従来の不便が改善されましたね。
2つ目は、プロミス (Promise) との組み合わせです。今回はサンプルコードを使って、この機能を解説します。
React 19 Betaの使い方(暫定版)
React 19 Betaの使い方は、React 19 Beta Upgrade GuideのInstallingに書かれています。npm install react@beta react-dom@beta
でベータ版をインストールすればよいようです。TypeScriptで使うには型定義が必要で、このドキュメントに書かれていますが、私の環境では上手く行きませんでした。なぜでしょうか??
そこで@types/react
に含まれているcanary.d.ts
を使う事で新しいHooksが使えるようにしました。vite-env.d.ts
ファイルにreact/canary
を追加することでVS Codeでcanary.d.ts
が参照されるようになります。
/// <reference types="vite/client" />
/// <reference types="react/canary" /> // ← 追加
Suspenseを思い出してみましょう
Suspenseに付いては、以前 React 18への予習シリーズ:Suspenseを復習しよう ブログに書きました。要約すると
- Suspense以前は
- fetch等でのデータ取得は適切なライフサイクルを管理する
- 通常
useEffect
内で起動する
- 通常
- 取得したデータはStateに格納
- 通信中(Loading…)管理は自前でState管理
- fetch等でのデータ取得は適切なライフサイクルを管理する
- Suspenseを使うと
- ライフサイクル管理はSuspenseが行ってくれる
- したがって取得したデータはState管理は不要で、通常のローカル変数に格納できる
- 通信中(Loading…)管理はSuspenseが行ってくれる
- データ取得はSuspensに対応した通信ライブラリー等を使う必要がある
- ライフサイクル管理はSuspenseが行ってくれる
Suspenseは通信などの非同期処理を含む、コンポーネントが簡単に作れます。
ただし、Suspenseを使うにはSuspensに対応した通信ライブラリー等が必要でした。従来はSWRやTanStack Query (旧React Query)を使う必要があしました(さらに、これらのライブラリーはSuspenseと同等の機能も持っていました😅)。
これらのライブラリーはキャッシュ機能も持っており有能なライブラリーですが、JavaScript標準のfetch等が使えないのは不便でした。
fetchを使うには自前でSuspense用に通信プロミスをThrowするコードが必要になります。→ 参考記事ブログ
use + Suspense のサンプルコード
いつものジャンケンアプリです。😃
ジャンケンの対戦結果はStateではなく、json-serverを使ったバックエンドで管理します。
- ① fetchを使ったバックエンドからのデータ取得用Promiseを戻す関数
- データを戻す関数なら
const scores = await res.json();
になりますが、ここではawait
がないのでPromiseを戻しています - 通信中(Loading…)がわかるようにsleepで0.5秒待ち時間を入れています
- データを戻す関数なら
- ② fetchを使ったバックエンドにデータを送る普通の関数
- ③ Suspenseに括られる対戦結果表示コンポーネント
- ここで、
use
Hookが使われています、引数はPromiseを戻すgetScores()
関数です
- ここで、
- ④ ジャンケンを行ってもState等の変化がないので location.reload() を使って強制的に画面の再表示を行っています(手抜きです😅)
- ⑤ Suspenseタグ、ShowScoreBoxコンポーネントを実行します
getScores
からthrowされたPromiseが実行中(通信中)はfallback=
が表示されます- 通信が終了しPromiseが完了すると、ShowScoreBoxコンポーネントが再実行されます。このときは通信結果があるのでthrowは起きず、ScoreBoxコンポーネントが実行され対戦結果が表示されます
import { Suspense, use, useReducer } from 'react';
import { Te, ScoreType, Jjudgment } from './JyankenType';
import JyankenBox from './JyankenBox';
import ScoreBox from './ScoreBox';
const BackendURL = "http://localhost:8000/jyanken";
const sleep = (sec: number) => new Promise(resolve => setTimeout(resolve,
sec * 1000));
const getScores = async (): Promise<ScoreType[]> => { // ← ①
const res = await fetch(BackendURL);
await sleep(0.5);
const scores = res.json();
return scores;
}
const postScore = async (score: ScoreType) => { // ← ②
await fetch(BackendURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(score)
});
}
function ShowScoreBox() { // ← ③
const scores = use(getScores());
return (<ScoreBox scores={scores} />);
}
export default function App() {
const pon = async (human: Te) => {
const computer: Te = Math.floor(Math.random() * 3);
const judgment: Jjudgment = (computer - human + 3) % 3;
const score = {human: human, computer: computer, judgment: judgment};
await postScore(score);
location.reload(); // ← ④
}
return (
<>
<h1>じゃんけん ポン!</h1>
<JyankenBox actionPon={te => pon(te)} />
<Suspense fallback={<p>⌛ Downloading scores...</p>}> {/* ← ⑤ */}
<ShowScoreBox />
</Suspense>
</>
);
}
まとめ
Susupenseは上にも書いたように、非同期処理を宣言的に書ける素晴らしいものです。しかし今までは、SWRやTanStack Queryのようなライブラリーを使わないと実現できませんでしたが。
use が導入されるとSusupenseが標準のJavaScript + Reactだけで実装できるようになるので、まだSusupenseを使ってない方はReact19がリリースされたら使ってみてくさい。