范式转移:组件不再都跑在浏览器
React 19 把 Server Components(RSC)从实验特性推向稳定,这是自 Hooks 以来最大的一次范式变化。核心思想很简单:默认在服务端渲染组件,只有需要交互的部分才下发到客户端。
带来的直接收益:
- 服务端组件可以直接访问数据库、文件系统、内部 API,不需要写 REST/GraphQL 中间层;
- 依赖不再被打进浏览器 bundle,首屏 JS 体积显著下降;
- 数据获取与渲染在同一处,天然避免瀑布流请求。
服务端组件:直接在组件里查数据库
// app/posts/page.tsx —— 默认是 Server Component
import { db } from "@/lib/db";
import LikeButton from "./like-button";
export default async function PostsPage() {
const posts = await db.post.findMany({
where: { published: true },
orderBy: { createdAt: "desc" },
take: 20,
});
return (
<ul>
{posts.map((p) => (
<li key={p.id}>
<h2>{p.title}</h2>
<p>{p.excerpt}</p>
{/* 只有这个按钮会被打包到客户端 */}
<LikeButton postId={p.id} initialLikes={p.likes} />
</li>
))}
</ul>
);
}注意:服务端组件不能有事件处理、不能用 useState/useEffect、不能访问 window。需要交互时提取一个带 "use client" 的子组件。
Actions:把表单提交变回“写函数”
// app/posts/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
export async function createPost(prevState: any, formData: FormData) {
const title = String(formData.get("title") ?? "").trim();
if (title.length < 3) {
return { error: "标题至少 3 个字符" };
}
await db.post.create({ data: { title, content: String(formData.get("content")) } });
revalidatePath("/posts");
return { ok: true };
}// app/posts/new-form.tsx
"use client";
import { useActionState, useOptimistic } from "react";
import { createPost } from "./actions";
export default function NewForm() {
const [state, formAction, pending] = useActionState(createPost, { ok: false });
return (
<form action={formAction}>
<input name="title" placeholder="标题" />
<textarea name="content" />
<button disabled={pending}>{pending ? "提交中…" : "发布"}</button>
{state.error && <p className="error">{state.error}</p>}
</form>
);
}useActionState 统一了“提交中、成功、失败”三态,配合 pending 不再需要手写 loading 布尔值。
乐观更新:useOptimistic
"use client";
import { useOptimistic, startTransition } from "react";
export default function LikeButton({ postId, initialLikes }: Props) {
const [likes, addOptimistic] = useOptimistic(initialLikes, (s, n: number) => s + n);
return (
<button
onClick={() =>
startTransition(async () => {
addOptimistic(1);
await fetch(`/api/like/${postId}`, { method: "POST" });
})
}
>
♥ {likes}
</button>
);
}use():在渲染中读取 Promise 与 Context
use() 是 React 19 新增的 API,与 Hook 不同,它可以在条件分支和循环中调用:
import { use, Suspense } from "react";
function Comments({ postId }: { postId: string }) {
const comments = use(fetchComments(postId)); // 挂起,直到 resolve
return <ul>{comments.map((c) => <li key={c.id}>{c.body}</li>)}</ul>;
}
export default function Page({ postId, showComments }: Props) {
return (
<article>
{/* use 可以在条件里用,这是 Hook 做不到的 */}
{showComments && (
<Suspense fallback={<p>加载评论…</p>}>
<Comments postId={postId} />
</Suspense>
)}
</article>
);
}迁移清单
forwardRef可以删了:ref现在作为普通 prop 传入函数组件。React.memo不再是默认手段:React 编译器会自动做记忆化。useMemo/useCallback可以大幅删减,交给编译器处理,代码更干净。- 注意 "use client" 的传染性:一旦文件标记为客户端,其所有导入都会进入 bundle,尽量把边界下沉到叶子组件。
小结
React 19 的方向是“把复杂度从开发者手里收回框架”:数据获取回到服务端、表单回到函数、记忆化交给编译器。理解 Server/Client 边界是使用好它的唯一关键。
评论 0
还没有评论,来说两句吧。