有兴趣了解我们的下一本书吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用程序

渲染模式

React 服务器组件

React 团队正在努力开发 零捆绑大小的 React 服务器组件,其目标是实现基于服务器的思维模型的现代用户体验。这与组件的服务器端渲染 (SSR) 相去甚远,并且可能导致客户端 JavaScript 包的大小显著减小

这项工作的方向令人兴奋,虽然它尚未投入生产,但值得您关注。以下资源可能对您有所帮助


服务器端渲染限制

如今,客户端 JavaScript 的服务器端渲染可能不尽如人意。您的组件的 JavaScript 在服务器上渲染成 HTML 字符串。此 HTML 被传递到浏览器,这似乎会导致快速的首屏内容绘制 (FCP) 或最大内容绘制 (LCP)。

但是,仍然需要获取 JavaScript 以进行交互,这通常通过水合步骤实现。服务器端渲染通常用于初始页面加载,因此水合后,您不太可能再次看到它被使用。

使用 React 服务器组件,我们的组件可以定期重新获取。具有在有新数据时重新渲染的组件的应用程序可以在服务器上运行,从而限制了需要发送到客户端的代码量。

[RFC]:开发人员必须不断地对使用第三方包进行选择。使用包来渲染一些 Markdown 或格式化日期对我们开发人员来说很方便,但它会增加代码大小并损害用户的性能

NoteWithMarkdown.js
1// *Before* Server Components
2import marked from "marked"; // 35.9K (11.2K gzipped)
3import sanitizeHtml from "sanitize-html"; // 206K (63.3K gzipped)
4
5function NoteWithMarkdown({text}) {
6 const html = sanitizeHtml(marked(text));
7 return (/* render */);
8}

服务器组件

React 的新型服务器组件补充了服务器端渲染,可以在不增加 JavaScript 包的情况下,将渲染内容转换为中间抽象格式。这既允许将服务器树与客户端树合并而不会丢失状态,又允许扩展到更多组件。

服务器组件并不是 SSR 的替代品。当它们一起使用时,它们支持以中间格式快速渲染,然后让服务器端渲染基础设施将其渲染成 HTML,使早期绘制仍然很快。我们对服务器组件发出的客户端组件执行 SSR,类似于 SSR 如何与其他数据获取机制一起使用。

但是这次,JavaScript 包将显著减小。早期的探索表明,包大小的收益可能很大(-18-29%),但 React 团队在完成进一步的基础设施工作后,将对实际收益有一个更清晰的了解。

[RFC]:如果我们将上面的示例迁移到服务器组件,我们可以对我们的功能使用完全相同的代码,但避免将其发送到客户端 - 代码节省超过 240K(未压缩)

NoteWithMarkdown.server.js
1import marked from "marked"; // zero bundle size
2import sanitizeHtml from "sanitize-html"; // zero bundle size
3
4function NoteWithMarkdown({text}) {
5 // same as before
6}

自动代码拆分

将仅在用户需要时提供用户需要的代码,一直被认为是最佳实践。这使您能够将应用程序分解成更小的包,从而减少需要发送到客户端的代码量。在服务器组件之前,需要手动使用 React.lazy() 来定义“拆分点”,或者依赖元框架(如路由/页面)来创建新的块。

PhotoRenderer.js
1// *Before* Server Components
2import React from "react";
3
4// 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"));
7
8function 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 语句替换为动态导入。
  • 它可能会延迟应用程序开始加载组件的时间,从而影响用户体验。

服务器组件引入了自动代码拆分,将客户端组件中的所有正常导入视为可能的代码拆分点。它们还允许开发人员更早地选择使用哪个组件(在服务器上),从而使客户端能够在渲染过程中的更早阶段获取它。

PhotoRenderer.server.js
1import React from "react";
2
3// 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";
6
7function 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 在服务器组件方面的工作。

有趣的相关主题