Kodokon kodokon.com

现代 React:Server Components、use、useTransition、useOptimistic

探索服务器组件、用 use 读取 Promise,以及 React 19 的乐观更新。

11 分钟 · 3 题

在 Kodokon 中打开本课

React Server Components(RSC)重新划定了客户端与服务器的边界。一个服务器组件只在服务器上运行:它的代码从不进入客户端打包产物,它可以是 async 的,能直接读取数据库或文件系统,其输出会作为一个序列化的流(RSC 载荷)发送给浏览器,React 将它与客户端树合并。作为代价,它没有状态、没有副作用、也无法访问浏览器:没有 useState,没有 useEffect,也没有事件处理器。它永远不会在客户端上重新渲染。

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,一个用于读取 Promisecontext 值的 API。与 hooks 不同,use 可以在条件或循环内部被调用。对于一个 Promise,它会挂起组件直到解决,依赖最近的 Suspense 边界。惯用的模式是:一个服务器组件创建这个 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 为网络变更补齐了这个组合:你立即显示所期望的状态,同时一个异步 action 在运行;当 action 结束时(无论成功或失败),React 会丢弃乐观值并再次显示真实状态,也就是来自真相来源的那个状态。

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 被允许打破 hooks 的哪条规则?
    • 在组件之外被调用
    • 在条件或循环内部被调用
    • 在模块提前 return 之后被调用
    • 在一个类内部被调用
  3. 当异步 action 结束时,useOptimistic 的值会怎样?
    • 它变成组件的永久状态
    • 它被丢弃,真实状态重新接管
    • 它与真实状态合并
    • 它一直被保留,直到下一次手动渲染