探索服务器组件、用 use 读取 Promise,以及 React 19 的乐观更新。
在 Kodokon 中打开本课React Server Components(RSC)重新划定了客户端与服务器的边界。一个服务器组件只在服务器上运行:它的代码从不进入客户端打包产物,它可以是 async 的,能直接读取数据库或文件系统,其输出会作为一个序列化的流(RSC 载荷)发送给浏览器,React 将它与客户端树合并。作为代价,它没有状态、没有副作用、也无法访问浏览器:没有 useState,没有 useEffect,也没有事件处理器。它永远不会在客户端上重新渲染。
// 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……但绝不能是普通函数或类实例。
'use client';
import { useState } from 'react';
export function LikeButton({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<button onClick={() => setCount(count + 1)}>
{count} likes
</button>
);
}React 19 引入了 use,一个用于读取 Promise 或 context 值的 API。与 hooks 不同,use 可以在条件或循环内部被调用。对于一个 Promise,它会挂起组件直到解决,依赖最近的 Suspense 边界。惯用的模式是:一个服务器组件创建这个 Promise 而不等待它,把它作为 prop 传递,然后一个客户端组件用 use 来消费它:服务器无需等待数据就开始流式发送页面。
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>
);
}在交互这一侧,useTransition 把一次更新标记为非紧急,并暴露 isPending,让你可以显示一个不显眼的指示器而不阻塞输入。useOptimistic 为网络变更补齐了这个组合:你立即显示所期望的状态,同时一个异步 action 在运行;当 action 结束时(无论成功或失败),React 会丢弃乐观值并再次显示真实状态,也就是来自真相来源的那个状态。
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.
});
}use API 被允许打破 hooks 的哪条规则?useOptimistic 的值会怎样?