渲染模式
React 服务器组件
React 团队正在努力开发 零捆绑大小的 React 服务器组件,其目标是实现基于服务器的思维模型的现代用户体验。这与组件的服务器端渲染 (SSR) 相去甚远,并且可能导致客户端 JavaScript 包的大小显著减小。
这项工作的方向令人兴奋,虽然它尚未投入生产,但值得您关注。以下资源可能对您有所帮助
- 值得阅读的 RFC,以及值得观看的 Dan 和 Lauren 的演讲。
- Next.js 中 React 18 的状态 以及 Next.js 的服务器组件路线图
- React 18 beta 版本状态
- Shopify Hydrogen 和服务器组件
服务器端渲染限制
如今,客户端 JavaScript 的服务器端渲染可能不尽如人意。您的组件的 JavaScript 在服务器上渲染成 HTML 字符串。此 HTML 被传递到浏览器,这似乎会导致快速的首屏内容绘制 (FCP) 或最大内容绘制 (LCP)。
但是,仍然需要获取 JavaScript 以进行交互,这通常通过水合步骤实现。服务器端渲染通常用于初始页面加载,因此水合后,您不太可能再次看到它被使用。
使用 React 服务器组件,我们的组件可以定期重新获取。具有在有新数据时重新渲染的组件的应用程序可以在服务器上运行,从而限制了需要发送到客户端的代码量。
[RFC]:开发人员必须不断地对使用第三方包进行选择。使用包来渲染一些 Markdown 或格式化日期对我们开发人员来说很方便,但它会增加代码大小并损害用户的性能
1// *Before* Server Components2import marked from "marked"; // 35.9K (11.2K gzipped)3import sanitizeHtml from "sanitize-html"; // 206K (63.3K gzipped)45function NoteWithMarkdown({text}) {6 const html = sanitizeHtml(marked(text));7 return (/* render */);8}
服务器组件
React 的新型服务器组件补充了服务器端渲染,可以在不增加 JavaScript 包的情况下,将渲染内容转换为中间抽象格式。这既允许将服务器树与客户端树合并而不会丢失状态,又允许扩展到更多组件。
服务器组件并不是 SSR 的替代品。当它们一起使用时,它们支持以中间格式快速渲染,然后让服务器端渲染基础设施将其渲染成 HTML,使早期绘制仍然很快。我们对服务器组件发出的客户端组件执行 SSR,类似于 SSR 如何与其他数据获取机制一起使用。
但是这次,JavaScript 包将显著减小。早期的探索表明,包大小的收益可能很大(-18-29%),但 React 团队在完成进一步的基础设施工作后,将对实际收益有一个更清晰的了解。
[RFC]:如果我们将上面的示例迁移到服务器组件,我们可以对我们的功能使用完全相同的代码,但避免将其发送到客户端 - 代码节省超过 240K(未压缩)
1import marked from "marked"; // zero bundle size2import sanitizeHtml from "sanitize-html"; // zero bundle size34function NoteWithMarkdown({text}) {5 // same as before6}
自动代码拆分
将仅在用户需要时提供用户需要的代码,一直被认为是最佳实践。这使您能够将应用程序分解成更小的包,从而减少需要发送到客户端的代码量。在服务器组件之前,需要手动使用 React.lazy()
来定义“拆分点”,或者依赖元框架(如路由/页面)来创建新的块。
1// *Before* Server Components2import React from "react";34// one of these will start loading *when rendered on the client*:5const OldPhotoRenderer = React.lazy(() => import("./OldPhotoRenderer.js"));6const NewPhotoRenderer = React.lazy(() => import("./NewPhotoRenderer.js"));78function Photo(props) {9 // Switch on feature flags, logged in/out, type of content, etc:10 if (FeatureFlags.useNewPhotoRenderer) {11 return <NewPhotoRenderer {...props} />;12 } else {13 return <PhotoRenderer {...props} />;14 }15}
代码拆分面临的一些挑战包括:
- 在元框架(如 Next.js)之外,您通常需要手动处理此优化,将
import
语句替换为动态导入。 - 它可能会延迟应用程序开始加载组件的时间,从而影响用户体验。
服务器组件引入了自动代码拆分,将客户端组件中的所有正常导入视为可能的代码拆分点。它们还允许开发人员更早地选择使用哪个组件(在服务器上),从而使客户端能够在渲染过程中的更早阶段获取它。
1import React from "react";23// one of these will start loading *once rendered and streamed to the client*:4import OldPhotoRenderer from "./OldPhotoRenderer.client.js";5import NewPhotoRenderer from "./NewPhotoRenderer.client.js";67function Photo(props) {8 // Switch on feature flags, logged in/out, type of content, etc:9 if (FeatureFlags.useNewPhotoRenderer) {10 return <NewPhotoRenderer {...props} />;11 } else {12 return <PhotoRenderer {...props} />;13 }14}
服务器组件会取代 Next.js SSR 吗?
不会。它们完全不同。服务器组件的初始采用实际上将通过元框架(如 Next.js)进行实验,因为研究和实验仍在继续。
为了总结 对 Dan Abramov 关于 Next.js SSR 和服务器组件区别的良好解释
- 服务器组件的代码永远不会传递到客户端。在使用 React 的许多 SSR 实现中,组件代码会通过 JavaScript 包传递到客户端。这可能会延迟交互。
- 服务器组件允许从树中的任何位置访问后端。使用 Next.js 时,您习惯于通过 getServerProps() 访问后端,它有只能在顶层页面工作的限制。随机的 npm 组件无法做到这一点。
- 服务器组件可以在维护树内的客户端状态的同时重新获取。这是因为主要传输机制比仅仅是 HTML 要丰富得多,允许重新获取服务器渲染的部分(例如搜索结果列表),而不会清除内部的状态(例如搜索输入文本、焦点、文本选择)
服务器组件的一些早期集成工作将通过 webpack 插件完成,该插件会
- 定位所有客户端组件
- 创建 ID => 块 URL 之间的映射
- Node.js 加载程序将对客户端组件的导入替换为对该映射的引用。
- 其中一些工作将需要更深层的集成(例如,与路由等部分的集成),这就是为什么让它与 Next.js 这样的框架一起使用非常有价值的原因。
正如 Dan 指出的,这项工作的一个目标是让元框架得到显著改善。
了解更多信息并与 React 团队分享反馈
要了解更多有关这项工作的信息,请观看 Dan 和 Lauren 的演讲,阅读RFC,并查看服务器组件演示以试用这项工作。感谢 Sebastian Markbåge、Lauren Tan、Joseph Savona 和 Dan Abramov 在服务器组件方面的工作。
有趣的相关主题