资讯动态

React Server Components 深度指南:结合 Refine 与 Next.js 落地服务端组件实践

发布时间:2026/9/10 0:22:41 来源:尧图企业网站定制
React Server Components 深度指南结合 Refine 与 Next.js 落地服务端组件实践【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineReact Server ComponentsRSC让组件彻底运行在服务端数据获取与渲染都发生在组件级别不再强制随客户端 JavaScript 一起打包下发。本文系统讲解 RSC 的运作机制、它与客户端组件及传统 SSR 的区别、导入规则与错误边界策略并结合 Refine 官方仓库中的 Next.js 集成文档 与 with-nextjs 示例应用 的实际源码展示服务端/客户端 Provider 拆分、服务端数据获取与权限守卫等可直接复用的实战方案。一、什么是 React Server ComponentsRSC 是 React 生态的新成员它允许创建仅在服务端运行的组件。在 RSC 中数据获取和一切副作用都发生在服务端渲染则以组件为粒度进行——由于取数逻辑与数据库、API 服务同处服务端省去了传统渲染模式下浏览器与服务端之间的往返请求。关键行为特征如下执行时机服务端组件在构建时执行一次或在用户访问端点时按需执行。你可以从数据库或 API 获取数据并渲染渲染出的结果是锁定的。代码留在服务端你在 Server Component 中写的代码保留在服务端不会被打包到前端。无 Hooks、无 Web API正因如此Server Components 不使用 React Hooks也无法访问浏览器 Web API。这些特性直接回答了RSC 想解决什么问题。二、RSC 试图解决的工程问题以一个电商产品页为例ProductPage渲染ProductDetails、ProductItem、MatchedItems三个子组件const ProductPage ({ productId }) { return ( ProductDetails productId{productId} ProductItem productId{productId} / MatchedItems productId{productId} / /ProductDetails / ); };方案 A父组件一次性取全部数据const ProductPage ({ productId }) { const data fetchContentsFromAPI(); return ( ProductDetails details{data.details} productId{productId} ProductItem item{data.product} productId{productId} / MatchedItems items{data.matchedItems} productId{productId} / /ProductDetails / ); };优点是体验一致——所有组件都等数据齐了再渲染。但问题同样明显耦合度高子组件紧耦合于父组件依赖父组件提供的数据难以维护违背单一职责子组件不为自己负责的数据负责加载时间长父组件一次性取回所有组件的数据任何一个慢请求都会拖垮整页。方案 B各组件各自取数职责内聚const ProductDetails ({ productId, children }) { const details fetchProductDetails(productId); return {children}/; }; const ProductItem ({ productId }) { const item fetchProductItem(productId); return /*...*/; }; const MatchedItems ({ productId }) { const items fetchMatchedItems(productId); return /*...*/; }; const ProductPage ({ productId }) { return ( ProductDetails productId{productId} ProductItem productId{productId} / MatchedItems productId{productId} / /ProductDetails / ); };好处是单一职责——每个组件为自己负责的数据负责。但副作用是体验割裂各子组件渲染时机取决于各自网络请求的响应速度用户会先看到页面的一部分再看到另一部分网络瀑布串行取数会造成典型的 network waterfall 问题。两种方案各有取舍但共享同一个根本限制都需要从客户端向服务端发起 API 调用从而增加延迟。这正是 React 团队引入 Server Components 的初衷——RSC 运行在服务端取数与渲染都比客户端组件更快。此外还带来一个附带收益既然运行在服务端就可以直接访问仅后端可用的服务数据库连接、内部密钥等。三、Server Components 与 Client Components 的核心区别最本质的区别Server Components 在服务端渲染一次Client Components 在客户端渲染并随用户交互持续重渲染。在传统的客户端 React 应用中用户请求页面时服务端只发送一个带若干script标签的空 HTML浏览器下载 JS 后再由客户端 JavaScript 渲染整页。而 Server Components 在服务端执行、不进入客户端 JS 包从而缩小 bundle 体积Client Components 则会发送到客户端、增大 bundle。两者的能力边界对照如下维度Server ComponentClient Component渲染位置服务端渲染一次客户端随交互重渲染React HooksuseState、useReducer、useEffect不可用不会重渲染全部可用浏览器 APIlocalStorage、sessionStorage等不可访问可访问异步函数体支持async/await可直接取数组件本身不能是 async需借助 Hooks 完成副作用是否计入客户端 bundle否是是否需要 Hydration否是一个实用提示要在.tsx组件里使用async/await需要 TypeScript 5.1 及以上版本5.1 解耦了 JSX 元素与 JSX 标签类型之间的类型检查。Refine 仓库当前的 Next.js 相关包如packages/nextjs-router均基于新版 TypeScript 工具链满足该前提。四、RSC 与 SSR服务端渲染不是一回事SSR把 React 组件在服务端渲染成一整份完整 HTML 发给客户端客户端 JS 仍然全量下发并接管整页交互。RSC与 SSR 配合通过一种中间结构类似 JSON 的协议把服务端组件的渲染结果传给客户端这些组件本身不向客户端交付任何 bundle。换句话说SSR 是服务端先画好图RSC 是一部分组件根本不下发到客户端。二者可以共存Next.js App Router 的页面默认既是服务端渲染的组件默认也是 Server Components。五、在 React 应用中编写 Server / Client Components5.1 最简 Server ComponentServer Components 可以在函数体内直接做副作用与异步取数// Server Component const BlogPost async ({ id, isEditing }) { const post await db.posts.get(id); return ( div h1{post.title}/h1 section{post.body}/section /div ); };5.2 最简 Client Component客户端组件就是普通 React 组件但文件顶部必须加use client指令作用类似use strict——它划定的是一个运行时边界// A client component use client; import React, { useState } from react; import { v4 as uuidv4 } from uuid; const PostEditor ({ blogPost }) { const [post, setPost] useStateany({ id: uuidv4(), title: blogPost.title, content: blogPost.content, }); const onChange (type: any, value: any) { switch (type) { case title: setPost({ ...post, title: value }); break; case content: setPost({ ...post, content: value }); break; default: break; } }; const submitPost () { // save blog post }; return ( div h1 classNamemy-4 text-centerCreate Post/h1 form onSubmit{submitPost} {/* Title 输入框、内容 textarea 与提交按钮略保持与原文档一致的表单结构 */} /form /div ); }; export default PostEditor;5.3 两条必须记住的导入规则规则一Server Components 不能被导入进 Client Components反过来可以。在 Server Component 中引用 Client Component 是合法的// Server Component import db from db; import PostEditor from PostEditor; async function BlogPost({ id, isEditing }) { const post await db.posts.get(id); return ( div h1{post.title}/h1 section{post.body}/section {isEditing ? PostEditor blogPost{post} / : null} /div ); }这里服务端取到post后仅在编辑态把数据作为 props 交给客户端的PostEditor表单——数据流从服务端流向客户端正是 RSC 的标准形态。规则二当 Client Component 在 Server Component 中渲染时可以把 Server Component 作为children传给它。const ServerComponent1 () { return ( ClientComponent ServerComponent2 / /ClientComponent ); };这条规则解释了为什么 Next.js 的Suspense、AntdRegistry这类外壳型客户端组件可以把任意内容作为 children 包裹。六、RSC 中的错误边界与错误处理RSC 的一大优势是错误可以在到达客户端之前就被服务端处理。以从 API 取数的 RSC 为例把取数逻辑包进 try-catchconst BlogPost async ({ id }) { try { const post await db.posts.get(id); return div{post.title}/div; } catch (error) { console.error(Error fetching post:, error); return divSomething went wrong. Please try again later./div; } };围绕这段代码生产实践有四个要点服务端捕获API 或数据库失败时返回降级 UI 或错误信息让客户端代码保持不受影响——用户不会看到坏掉的组件。用户反馈即便错误在服务端被消化UI 上仍应给出友好的错误提示让用户知道发生了什么、正在处理。日志与监控错误发生在服务端接入 Sentry 等日志服务可以自动发现并上报问题定位速度远快于客户端。非关键数据的降级对非核心 UI 区域可以渲染默认组件或 loading 状态而不直接报错保证应用核心功能仍可用。七、何时该用 React Server Components适合的场景更快的首屏加载RSC 显著缩短 Web 应用加载时间大型复杂应用在客户端交互难以维护的复杂应用中优势最明显无需即时客户端交互的组件静态内容展示类组件优先交给服务端SEO 收益服务端渲染配合 RSC 让搜索引擎直接索引到预渲染内容。反过来表单、图表拖拽、实时计数器这类强交互逻辑仍应留在 Client Components。八、在 Next.js 应用中使用 Server Components在本文写作所对应的时间点Next.js 是 RSC 唯一稳定可用的实现。任何使用 App Router 的 Next.js 项目组件默认就是 Server Component无需任何声明只有需要交互时才显式加use client。Server Component 示例与第五节相同此处强调路径约定app/BlogPost.tsx与use client声明的app/PostEditor.tsx示例见 5.1 / 5.2 节 的代码规则完全一致。九、仓库实战Refine Next.js 示例中的 RSC 边界划分Refine 的官方 with-nextjs 示例 是一个Ant Design Next.js App Router的完整后台模板它的文件组织恰好是 RSC 边界划分的教科书式样本。9.1 根 Layout服务端组件 Suspenseexamples/with-nextjs/src/app/layout.tsx 本身是 Server Component没有use client它直接调用 Next.js 的服务端 API 读取 Cookie把主题值作为初始值传给客户端上下文import { cookies } from next/headers; import React, { Suspense } from react; export default function RootLayout({ children }: Readonly{ children: React.ReactNode }) { const cookieStore cookies(); const theme cookieStore.get(theme); return ( html langen body Suspense AntdRegistry {/* ... RefineKbarProvider / ColorModeContextProvider / DevtoolsProvider */} Refine routerProvider{routerProvider} dataProvider{dataProvider} notificationProvider{useNotificationProvider} authProvider{authProviderClient} resources{[/* blog_posts、categories 资源定义 */]} options{{ syncWithLocation: true, warnWhenUnsavedChanges: true }} {children} RefineKbar / /Refine {/* ... */} /AntdRegistry /Suspense /body /html ); }两个细节值得注意cookies()来自next/headers只能在服务端调用——这行代码本身就证明了该文件运行在服务端对应第五节Server Components 不支持浏览器 API、但支持服务端 API的结论整个Refine组件树被Suspense包裹为后续数据预取与流式渲染留出空间。9.2 为什么Refine /必须是客户端组件示例里几乎所有页面都显式声明了use client例如 blog-posts 列表页 第一行就是use client——因为它大量使用useTable、useMany等基于 React Query / Hooks 的 API这些在 Server Components 中不可用Server Components 不能重渲染、不能持有状态。Refine 官方 Next.js 集成文档对此有明确解释由于Refine /深度依赖 React context 与 React state它必须是客户端组件而函数不能直接传递给 Client Components除非用use server显式暴露所以dataProvider、authProvider等必须标记为客户端函数。文档见 FAQ 小节。9.3 一个组件、两个运行时authProvider 的 client / server 拆分这是该示例最有价值的设计。Refine 的AuthProvider被拆成两个文件分别服务两种运行时客户端版本auth-provider.client.ts文件首行use client通过js-cookie读写浏览器 Cookie实现login、register、check、getPermissions、getIdentity、onError等完整方法示例中使用adminrefine.dev等 mock 用户登录成功后把用户信息写入 30 天有效的authCookie。服务端版本auth-provider.server.ts没有任何use client标记直接使用 Next.js 的cookies()在 SSR 阶段做认证判断import type { AuthProvider } from refinedev/core; import { cookies } from next/headers; export const authProviderServer: PickAuthProvider, check { check: async () { const cookieStore cookies(); const auth cookieStore.get(auth); if (auth) { return { authenticated: true }; } return { authenticated: false, logout: true, redirectTo: /login, }; }, };两个文件读的是同一枚authCookie但走的 API 不同服务端用next/headers客户端用js-cookie。这正是文档 FAQ 给出的标准解法——当你的 Provider 需要同时服务两端时为每个运行时创建独立文件再在各自的位置导入对应版本从而绕开client function 不能从服务端调用的限制。9.4 服务端取数、认证守卫与权限控制官方 Next.js 集成文档index.md给出了在 Server Components 中直接调用 Provider 方法的标准写法服务端认证守卫 数据获取app/blog-posts/layout.tsimport { authProvider } from providers/auth-provider; import { redirect } from next/navigation; export default async function IndexPage() { const { hasAuth, hasPermission, data } await getData(); if (!hasAuth) { return redirect(/login); } return ( div h1Posts/h1 ul {data?.map((post: any) ( li key{post.id}{post.title}/li ))} /ul /div ); } async function getData() { const hasAuth await authProvider.check(); let data null; if (hasAuth hasPermission) { data await dataProvider.getList({ resource: posts, }); } return { hasAuth, data }; }这里redirect(/login)发生在服务端渲染之前未登录用户根本拿不到页面 HTML——比客户端跳转更早、更彻底地拦截访问。服务端访问控制app/posts/page.tsxaccessControlProvider的can方法同样可以在普通 async 函数中调用后用于 Server Componentsexport default async function PostList() { const { can } await getData(); if (!can) { return h1Unauthorized/h1; } return ( div h1Posts/h1 /div ); } async function getData() { const { can } await accessControlProvider.can({ resource: posts, action: list, }); return { can }; }文档同时建议页面级的访问控制优先用服务端方案客户端的CanAccess组件作为补充。服务端直出列表数据SSR 场景直接用dataProvider.getList在服务端取数并渲染import dataProvider from refinedev/simple-rest; const API_URL https://api.fake-rest.refine.dev; export default async function ProductList() { const { posts, total } await getData(); return ( div h1Posts ({total})/h1 hr / {posts.map((post) ( div key{post.id} h1{post.title}/h1 p{post.body}/p /div ))} /div ); }此外如果需要在 Server Components 中解析表格的 URL 参数分页、排序等文档特别指出可以从refinedev/nextjs-router的 parse-table-params 模块 导入对应工具函数而不是把客户端 hook 搬进服务端。9.5 dataProvider 的归属data-provider/index.ts 整体标记为use client导出的是refinedev/simple-rest指向https://api.fake-rest.refine.dev的 provider 实例作为客户端函数传入Refine /。若你希望同一份取数逻辑在服务端组件中复用就参照 9.3 的方式拆出 server 版本直接以普通 async 函数形式调用dataProvider.getList(...)即可。十、RSC 的优缺点优点bundle 更小Server Components 的代码留在服务端不计入前端 JS 体积第三方库可以无感地用于服务端逻辑安全性提升API 密钥、数据库连接串、凭据等敏感信息只出现在服务端代码中延迟降低API 调用就近发生在服务端省去客户端往返SEO 更好只有生成的 HTML 发给客户端搜索引擎更容易索引整体性能提升取数与渲染都在服务端完成。缺点只存在于 Meta 框架中目前只能通过 Next.js 使用原生 ReactVite/CRA 等用不了心智模型成本高RSC 引入了新的范式哪些代码能跑在哪端、函数跨端如何传递学习和排错成本都不低——第九章示例中 client/server Provider 拆分、use client边界划分都是这种成本的直接体现。十一、进阶Hydration 与 RSCHydration水合指 React 把事件监听器挂载到已渲染好的 HTML 上使其变为可交互。RSC 只运行在服务端产出静态 HTML、不发送任何该组件的 JavaScript因此天然不需要 hydration只有 Client Components 需要水合。基础示例——不会被水合的服务端组件// ServerComponent.tsx - This wont be hydrated export const ServerComponent async () { const data await fetchSomeData(); return ( div h1{data.title}/h1 p{data.description}/p /div ); };会被水合的客户端组件// ClientComponent.tsx - This will be hydrated use client; // Flagging it as a client component import { useState } from react; export const ClientComponent () { const [count, setCount] useState(0); return ( div button onClick{() setCount(count 1)}Increment: {count}/button /div ); };在 Next.js 中二者可以同页共存默认所有组件都是 Server Component客户端组件必须显式声明// app/BlogPage.tsx - Next.js Example using both Server and Client components import { ServerComponent } from ./ServerComponent; import { ClientComponent } from ./ClientComponent; export default function BlogPage() { return ( div {/* ServerComponent will be rendered as static HTML on the server */} ServerComponent / {/* ClientComponent will be hydrated on the client for interactivity */} ClientComponent / /div ); }水合的具体流程是三步客户端组件的静态 HTML 已随页面渲染完成React JS 包下载完成后React 查找既有 HTML 标记并把事件监听器挂到对应 DOM 节点上组件变为可交互如计数器按钮的onClick生效。而像下例这样的纯展示型服务端组件取数在服务端完成后只输出 HTML客户端没有任何需要下载的执行逻辑// ServerComponent.tsx - Non-interactive server component export const StaticContent async () { const data await fetch(https://api.example.com/data); const result await data.json(); return ( div h1{result.title}/h1 p{result.description}/p /div ); };性能含义很直接水合范围越小客户端工作量越少。实践原则是——只在确需交互表单、按钮、输入框时才声明 Client Component静态内容与展示型逻辑尽量交给 Server Components保持客户端 bundle 精简。十二、结语React Server Components 把取数、渲染、错误处理的主动权部分归还给了服务端更小的 bundle、更早的认证拦截、更直接的敏感信息保护以及更简单的 SEO 结构。从 Refine 仓库的 with-nextjs 示例 与 Next.js 集成文档 可以看到一套清晰的落地范式交互密集型组件Refine /、useTable页面显式声明为 Client ComponentProvider 按运行时拆分为 client / server 双版本而认证守卫、权限判断与列表预取则留在 Server Components 中用普通 async 函数完成。理解了这条边界线你就能在 Next.js 应用里自如地组合两种组件模型。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价