React 服务器组件 ✔

React 团队正在研究零包大小的 React 服务器组件,旨在通过服务器驱动的思维模型实现现代 UX。
这与组件的服务器端渲染 (SSR) 完全不同,并且可能导致客户端 JavaScript 包显着变小。

这项工作的方向令人兴奋,虽然尚未准备好投入生产,但值得关注。 以下资源可能会引起您的兴趣:


服务器端渲染限制

今天的客户端 JavaScript 的服务器端呈现可能是次优的。

组件的 JavaScript 在服务器上呈现为 HTML 字符串。此 HTML 被传送到浏览器,这可能会导致快速的“首次内容绘制”或“最大内容绘制”。

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

注意:虽然确实可以利用 SSR 构建一个仅限服务器的 React 应用程序并完全避免在客户端上加水,但模型中的大量交互通常涉及跳出 React。
服务器组件启用的混合模型将允许在每个组件的基础上决定这一点。

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

[RFC]:开发人员必须不断地选择使用第三方软件包。 使用包来呈现一些降价或格式化日期对我们作为开发人员来说很方便,但它增加了代码大小并损害了我们用户的性能

1
2
3
4
5
6
7
8
9

// *Before* Server Components
import marked from "marked"; // 35.9K (11.2K gzipped)
import sanitizeHtml from "sanitize-html"; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

Server Components

React 的新服务器组件补充了服务器端渲染,无需添加到 JavaScript 包即可渲染为中间抽象格式。
这既允许将服务器树与客户端树合并而不会丢失状态,并且可以扩展到更多组件。

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

然而,这一次,JavaScript 包会小很多。 早期的探索表明,bundle 大小的胜利可能很重要(-18-29%),
但一旦进一步的基础设施工作完成,React 团队将对野外获胜有更清晰的认识。

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

1
2
3
4
5
6
7

import marked from "marked"; // zero bundle size
import sanitizeHtml from "sanitize-html"; // zero bundle size

function NoteWithMarkdown({text}) {
  // same as before
}

自动代码拆分

通过使用代码拆分只为用户需要的代码提供服务被认为是最佳实践。
这使您可以将您的应用程序分解为更小的包,需要将更少的代码发送到客户端。

在服务器组件出现之前,人们会手动使用 React.lazy() 来定义“分割点”或依靠元框架的启发式设置,例如路由/页面来创建新的块。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

// *Before* Server Components
import React from "react";

// 其中之一将开始加载*在客户端上呈现时*:
const OldPhotoRenderer = React.lazy(() => import("./OldPhotoRenderer.js"));
const NewPhotoRenderer = React.lazy(() => import("./NewPhotoRenderer.js"));

function Photo(props) {
  // 切换功能标签、登录/注销、内容类型等:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />;
  } else {
    return <PhotoRenderer {...props} />;
  }
}

代码拆分的一些挑战是:

  • 在元框架(如 Next.js)之外,通常必须手动解决此优化问题,用动态导入替换导入语句。

  • 当应用程序开始加载影响用户体验的组件时,它可能会延迟。

服务器组件引入了自动代码拆分,将客户端组件中的所有正常导入视为可能的代码拆分点。

它们还允许开发人员更早地(在服务器上)选择要使用的组件,从而允许客户端在渲染过程中更早地获取它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

import React from "react";

// 其中之一将开始加载*一旦呈现并流式传输到客户端*:
import OldPhotoRenderer from "./OldPhotoRenderer.client.js";
import NewPhotoRenderer from "./NewPhotoRenderer.client.js";

function Photo(props) {
  // 切换功能标签、登录/注销、内容类型等:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />;
  } else {
    return <PhotoRenderer {...props} />;
  }
}

服务器组件会取代 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 这样的框架一起工作将是有价值的。

正如丹指出的那样,这项工作的目标之一是使元框架变得更好。


了解更多信息并与 React 团队分享反馈

要了解有关这项工作的更多信息,请观看 Dan 和 Lauren 的演讲,
阅读 RFC 并查看服务器组件演示以进行这项工作。
感谢 Sebastian Markbåge、Lauren Tan、Joseph Savona 和 Dan Abramov 在服务器组件方面所做的工作。

有趣的相关主题:


知识点