Kodokon kodokon.com

モダンな React: Server Components、use、useTransition、useOptimistic

サーバーコンポーネント、use による Promise の読み取り、そして React 19 の楽観的更新を探ります。

11 分 · 3 問

このレッスンを Kodokon で開く

React Server Components(RSC)はクライアントとサーバーの境界を引き直します。サーバーコンポーネントは サーバー上でのみ 実行されます。そのコードはクライアントバンドルに一切届かず、async にでき、データベースやファイルシステムを直接読み取ることができ、その出力は React がクライアントツリーとマージする シリアライズされたストリーム(RSC ペイロード)としてブラウザに送信されます。その代わり、state もエフェクトもブラウザへのアクセスもありませんuseStateuseEffect もイベントハンドラーもありません。クライアント上で再レンダーされることは決してありません。

JSX
// Server component: never sent to the client.
export default async function NotesPage() {
  const notes = await db.notes.findAll();
  return (
    <ul>
      {notes.map((note) => (
        <li key={note.id}>{note.title}</li>
      ))}
    </ul>
  );
}
非同期のサーバーコンポーネントがデータベースを直接クエリします。

インタラクティビティは クライアントコンポーネント に存在し、ファイル先頭の 'use client' ディレクティブでマークされます。このディレクティブは「クライアントコンポーネント」をマークするのではなく 境界 をマークします。そのモジュールがインポートするものはすべて、同様にクライアント側へ移ります。境界を越える props は シリアライズ可能 でなければなりません。プレーンなオブジェクト、配列、Promise などは可能ですが、通常の関数やクラスのインスタンスは決して渡せません。

JSX
'use client';

import { useState } from 'react';

export function LikeButton({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count} likes
    </button>
  );
}
'use client' ディレクティブはインタラクティブな領域を区切ります。

React 19 は use を導入します。これは Promise または コンテキスト の値を読み取る API です。フックとは異なり、use条件やループの内部で 呼び出せます。Promise を渡された場合、解決までコンポーネントをサスペンドし、最も近い Suspense 境界に依存します。慣用的なパターン: サーバーコンポーネントが await せずに Promise を 生成 し、それを prop として渡し、クライアントコンポーネントが use でそれを 消費 します。こうすることで、サーバーはデータを待たずにページのストリーミングを開始できます。

JSX
import { use, Suspense } from 'react';

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);
  return comments.map((c) => (
    <p key={c.id}>{c.text}</p>
  ));
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<Spinner />}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}
use は Promise が解決されるまでコンポーネントをサスペンドします。

インタラクション側では、useTransition が更新を非緊急としてマークし、isPending を公開するので、入力をブロックせずに控えめなインジケーターを表示できます。useOptimistic はネットワークミューテーションのためにこのデュオを完成させます。非同期アクションが実行される間、期待される state を即座に表示し、アクションが終わると(成功でも失敗でも)、React は 楽観的な値を破棄 し、信頼できる情報源からの実際の state を再び表示します。

JSX
const [optimisticLikes, addOptimisticLike] =
  useOptimistic(likes, (count, delta) => count + delta);

function handleLike() {
  startTransition(async () => {
    addOptimisticLike(1);
    await saveLike();
    // At the end, React shows the real state again.
  });
}
インターフェースは即座に反応し、サーバーは後から確認します。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. サーバーコンポーネントができないことは何ですか?
    • async として宣言し、リクエストを await する
    • データベースを直接読み取る
    • useState とイベントハンドラーを使う
    • クライアントコンポーネントを子としてレンダリングする
  2. use API が破ってよいフックのルールはどれですか?
    • コンポーネントの外で呼び出されること
    • 条件やループの内部で呼び出されること
    • モジュールの早期リターンの後に呼び出されること
    • クラスの内部で呼び出されること
  3. 非同期アクションが完了すると、useOptimistic の値はどうなりますか?
    • コンポーネントの恒久的な state になる
    • 破棄され、実際の state が再び引き継ぐ
    • 実際の state とマージされる
    • 次の手動レンダーまで保持される