原文链接:Making Sense of React Server Components (joshwcomeau.com)
此译文对原文些许的删除和修改
几个月前,React 团队推出了 React Server Components,这是最新的范式转变。React 组件首次可以完全在服务器上运行。
关于这个问题在网上引起了很多混乱。很多人对这是什么、它是如何工作的、有什么好处以及它如何与服务器端渲染等事物结合在一起有很多问题。
快速了解服务器端渲染
如果你已经熟悉 SSR,可以跳过到下一个标题。
大多数 React 设置都使用了"客户端"渲染策略。用户会收到一个看起来像这样的 HTML 文件:
html
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
</body>
</html>
bundle.js 脚本包含了我们需要挂载和运行应用程序的所有内容,包括 React、其他第三方依赖和我们编写的所有代码。
一旦 JS 被下载和解析,React 就会开始工作,为我们的整个应用程序创建所有的 DOM 节点,并将其放置在那个空的 <div id="root">
中。
这种方法的问题在于需要花费时间来完成所有的工作。而在这一切发生的同时,用户只能盯着一片空白的白屏。随着时间的推移,这个问题会变得越来越严重:每次我们发布新功能,都会给我们的 JavaScript 包添加更多的内容,从而延长用户等待的时间。
服务器端渲染旨在改善这种体验。服务器不再发送一个空的 HTML 文件,而是渲染我们的应用程序以生成实际的 HTML。用户将收到一个完整的 HTML 文档。
这个 HTML 文件仍然会包含 <script>
标签,因为我们仍然需要在客户端上运行 React 来处理任何交互。但是我们会在浏览器中配置 React 以稍微不同的方式工作:它不再从头开始创建所有的 DOM 节点,而是采用现有的 HTML。这个过程被称为 _hydration(_水合)
。
水合就像给"干燥"的HTML浇上互动和事件处理的"水"。
一旦 JS 捆绑包下载完成,React 将快速运行整个应用程序,构建 UI 的虚拟草图,并将其"适配"到真实的 DOM 上,附加事件处理程序,触发任何效果等等。
所以,这就是 SSR 的要点。服务器生成初始的 HTML,这样用户就不必盯着一个空白页面等待 JS 捆绑包的下载和解析。客户端的 React 接管服务器端的 React 继续工作,接管 DOM 并添加交互性。
当我们谈论服务器端渲染时,想象这样一个流程:
- 用户访问 myWebsite.com。
- 一个 Node.js 服务器接收到请求,并立即渲染 React 应用程序,生成 HTML。
- 这个新鲜出炉的 HTML 被发送到客户端。
这是一种实现服务器端渲染的可能方式,但不是唯一的方式。另一种选择是在构建应用程序时生成 HTML。
通常,React 应用程序需要进行编译,将 JSX 转换为普通的 JavaScript,并将所有模块捆绑在一起。如果在同一过程中,我们为所有不同的路由"预渲染"了所有 HTML 呢? 这通常被称为静态网站生成(SSG)。它是服务器端渲染的一种子变体。 在我看来,"服务器端渲染"是一个包含多种不同渲染策略的总称。它们都有一个共同点:初始渲染发生在像 Node.js 这样的服务器运行时中,使用
ReactDOMServer API
。这实际上并不重要,无论是按需还是在编译时进行。无论哪种方式,都属于服务器端渲染。
Bouncing back and forth 来回反弹
让我们来谈谈 React 中的数据获取。通常情况下,我们有两个独立的应用程序通过网络进行通信:
- 一个客户端的 React 应用程序
- 一个服务器端的 REST API
使用类似 React Query、SWR 或 Apollo 的工具,客户端会向后端发起网络请求,后端从数据库中获取数据并通过网络发送回客户端。
图表展示了使用客户端渲染(CSR)策略的流程。它从客户端接收一个 HTML 文件开始。这个文件没有任何内容,但是它包含一个或多个 <script>
标签。
一旦 JS 被下载并解析,我们的 React 应用程序将启动,创建一堆 DOM 节点并填充UI。但是一开始,我们没有任何实际数据,所以只能渲染带有加载状态的外壳(标题、页脚、通用布局)。
你可能经常看到这种模式。例如,UberEats 在获取需要填充实际餐厅数据之前,先渲染一个外壳。
用户将在网络请求解决并 React 重新渲染后看到这个加载状态,随后,加载 UI 将被真实内容替换。
让我们看看另一种架构方式。下一个图表保持了相同的数据获取模式,但使用了服务器端渲染而不是客户端渲染。
在这个新的流程中,我们在服务器上执行第一次渲染。这意味着用户收到的 HTML 文件不是完全空白的。
这是一个改进:一个外壳比一个空白的白页要好 - 但最终,它并没有以显著的方式推动进展。用户访问我们的应用程序不是为了看到一个加载屏幕,而是为了看到内容(餐厅、酒店列表、搜索结果、消息等)。
为了真正了解用户体验的差异,让我们在图表中添加一些网络性能指标。在这两个流程之间切换,并注意旗帜发生的变化。
这些标志代表常用的网页性能指标。以下是详细解释:
- 首次绘制(First Paint)- 用户不再盯着空白的白屏。页面的大致布局已经渲染出来,但内容仍未加载。有时也称为 FCP(首次有内容绘制)。
- 页面交互(Page Interactive) - React 已经下载完成,我们的应用程序已经被渲染/注水。交互元素现在完全响应。有时被称为 TTI(交互时间)。
- 内容绘制(Content Paint) - 页面现在包含用户关心的内容。我们从数据库中提取了数据并在用户界面中进行了渲染。有时被称为 LCP(最大内容绘制)。
通过在服务器上进行初始渲染,我们能够更快地绘制出初始的"外壳"。这可以使加载体验感觉更快一些,因为它提供了进展的感觉,事情正在发生。
而且,在某些情况下,这将是一个有意义的改进。例如,也许用户只是在等待标题加载完成,以便他们可以点击导航链接。
当我们看到 SSR 图表的时候,我们注意到请求是从服务器开始的。为什么不在初始请求期间完成数据库工作,而不是需要进行第二次网络请求呢?
换句话说,为什么不像这样做呢?
不必在客户端和服务器之间来回传递,我们将数据库查询作为初始请求的一部分,并将完全填充的用户界面直接发送给用户。
但是,嗯,我们应该如何做到这一点呢?
为了使这个方法起作用,我们需要能够给 React 提供一段代码,它在服务器上独立运行以进行数据库查询。
但是,React 并没有提供这个选项,即使使用了服务器端渲染,我们的所有组件仍然在服务器和客户端上渲染。
生态系统已经提出了许多解决方案来解决这个问题。像 Next.js 和 Gatsby 这样的元框架已经创造了自己的方式来仅在服务器上运行代码。
例如,这是使用Next.js(使用传统的"Pages"路由器)的样子:
jsx
import db from 'imaginary-db';
// This code only runs on the server:
export async function getServerSideProps() {
const link = db.connect('localhost', 'root', 'passw0rd');
const data = await db.query(link, 'SELECT * FROM products');
return {
props: { data },
};
}
// This code runs on the server + on the client
export default function Homepage({ data }) {
return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}
让我们来分解一下:当服务器收到请求时,会调用 getServerSideProps 函数。它返回一个 props 对象。然后将这些属性传递给组件,该组件首先在服务器上呈现,然后在客户端上进行注水。
这里的聪明之处在于 getServerSideProps 在客户端上不会重新运行。实际上,这个函数甚至不包含在我们的 JavaScript 包中!
这种方法超前于它的时代。老实说,它相当棒。但是它也有一些缺点:
- 这个策略只适用于路由级别的组件,也就是树的顶层组件。我们不能在任何组件中使用这个方法。
- 每个元框架都有自己的方法。Next.js 有一种方法,Gatsby 有另一种方法,Remix 有另一种方法。它还没有被标准化。
- 我们所有的 React 组件都会在客户端上进行注水,即使没有必要这样做。
多年来,React 团队一直在默默地研究这个问题,试图找到一种官方的解决方案。他们的解决方案被称为 React 服务器组件。
React Server Components 简介
从高层次来看,React Server Components 是一个全新范式的名称。在这个新世界中,我们可以创建仅在服务器上运行的组件。这使我们能够在 React 组件中直接编写数据库查询等操作!
这是一个"服务器组件"的快速示例:
jsx
import db from 'imaginary-db';
async function Homepage() {
const link = db.connect('localhost', 'root', 'passw0rd');
const data = await db.query(link, 'SELECT * FROM products');
return (
<>
<h1>Trending Products</h1>
{data.map((item) => (
<article key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</article>
))}
</>
);
}
export default Homepage;
这段代码非常疯狂,理论上,"函数组件不能是异步的!而且我们不允许在渲染中直接产生副作用!"
要理解的关键是:服务器组件永远不会重新渲染。它们在服务器上运行一次以生成用户界面。渲染的值被发送到客户端并锁定在原地。就 React 而言,这个输出是不可变的,永远不会改变。
这意味着 React 的 API 中有很大一部分与服务器组件不兼容。例如,我们不能使用状态,因为状态可以改变,但服务器组件无法重新渲染。我们也不能使用副作用,因为副作用只在客户端渲染后运行,而服务器组件永远不会到达客户端。
这也意味着在规则方面我们有更多的灵活性。例如,在传统的 React 中,我们需要将副作用放在回调函数、事件处理程序或其他地方,以防止它们在每次渲染时重复执行。但是如果组件只运行一次,我们就不必担心这个问题!
服务器组件本身非常简单,但"React Server Components"范式则更为复杂。这是因为我们仍然有常规的组件,它们的组合方式可能相当令人困惑。
在这个新的范式中,我们熟悉的"传统"React组件被称为客户端组件。老实说,我不太喜欢这个名字。 😅
"Client Component"这个名称暗示了这些组件只在客户端渲染,但实际上并不是这样。客户端组件在客户端和服务器上都进行渲染。
总结一下这些术语:
- React 服务器组件是这种新范式的名称。
- 在这个新的范式中,我们所熟悉和喜爱的"标准"React 组件已被重新命名为客户端组件。这是一个对旧事物的新称呼。
- 这种新的范式引入了一种新类型的组件,即服务器组件。这些新组件仅在服务器上进行渲染。它们的代码不包含在 JS 捆绑包中,因此它们永远不会进行水合或重新渲染。
React 服务器组件与服务器端渲染 让我们澄清另一个常见的困惑:React 服务器组件不是服务器端渲染的替代品。您不应将 React 服务器组件视为 "SSR版本2.0"。 相反,我更喜欢将其看作是两个完美拼合的拼图,两种相互补充的味道。 我们仍然依赖于服务器端渲染来生成初始的 HTML。React 服务器组件在此基础上,允许我们在客户端 JavaScript 包中省略某些组件,确保它们只在服务器上运行。 实际上,即使不使用服务器端渲染,也可以使用 React 服务器组件,但在实践中,如果同时使用它们,效果会更好。 React 团队已经构建了一个没有服务器端渲染的最小 RSC(minimal RSC demo) 演示。
Compatible Environments 兼容的环境
所以,通常情况下,当一个新的 React 功能发布时,我们可以通过将 React 依赖项升级到最新版本来在我们现有的项目中开始使用它。一个快速的 npm install react@latest ,我们就可以开始了。
不幸的是,React Server Components的工作方式并不是这样的。
React Server Components 需要与 React 之外的一堆东西紧密集成,比如打包工具、服务器和路由器。
目前只有一种方法可以开始使用 React Server Components,那就是使用 Next.js 13.4+,并使用他们全新重新架构的 "App Router"。
希望将来有更多基于 React 的框架开始融入 React Server Components。感觉很奇怪,一个核心的 React 功能只在一个特定的工具中可用!React 文档中有一个"最前沿的框架"部分,列出了支持 React Server Components 的框架。
Specifying client components 指定客户端组件
在这个新的"React Server Components"范式中,默认情况下,所有组件都被假定为服务器组件。我们必须选择"加入"客户端组件。
我们通过指定一个全新的指令来实现这一点:
jsx
'use client';
import React from 'react';
function Counter() {
const [count, setCount] = React.useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Current value: {count}
</button>
);
}
export default Counter;
顶部的独立字符串 'use client' 是我们向 React 发出的信号,表明这个文件中的组件是客户端组件,它们应该被包含在我们的 JS 捆绑包中,以便它们可以在客户端重新渲染。
这种方式可能看起来非常奇怪,用来指定我们正在创建的组件类型,但是这种做法是有先例的:JavaScript 中的 "use strict" 指令,它选择进入"严格模式"。
我们在服务器组件中不指定 'use server' 指令;在 React 服务器组件范式中,默认将组件视为服务器组件。
哪些组件应该是客户端组件? 你可能会想:我应该如何决定一个给定的组件是应该是服务器组件还是客户端组件? 通常情况下,如果一个组件可以是服务器组件,那么它应该是一个服务器组件。服务器组件往往更简单,更容易理解。还有一个性能优势:因为服务器组件不在客户端运行,它们的代码不会包含在我们的 JavaScript 包中。React 服务器组件范式的一个好处是它有潜力改善页面交互(TTI)指标。 话虽如此,我们也不应该把消灭尽可能多的客户端组件作为我们的使命!我们不应该试图优化最小数量的客户端组件。直到现在,每个 React 应用中的每个 React 组件都是客户端组件。 当你开始使用React服务器组件时,你可能会发现这是相当直观的。我们的一些组件需要在客户端运行,因为它们使用状态变量或效果。你可以在这些组件上添加一个 'use client' 指令。否则,你可以将它们保留为服务器组件。
Boundaries 边界
当我开始熟悉 React 服务器组件时,我最初的一个问题是:当 props 发生变化时会发生什么?
例如,假设我们有一个像这样的服务器组件:
jsx
function HitCounter({ hits }) {
return (
<div>
Number of hits: {hits}
</div>
);
}
假设在初始的服务器端渲染中, hits 等于 0 。那么,这个组件将生成以下标记:
html
<div>
Number of hits: 0
</div>
但是,如果 hits 的值发生了变化会发生什么呢?假设它是一个状态变量,并且它从 0 变为 1 。 HitCounter 需要重新渲染,但它无法重新渲染,因为它是一个服务器组件!
问题是,服务器组件在孤立状态下并没有太多意义。我们需要放大视角,采取更全面的观点,考虑我们应用程序的结构。
假设我们有以下组件树:
如果所有这些组件都是服务器组件,那么一切都说得通。因为这些组件都不会重新渲染,所以没有任何属性会发生变化。
但假设 Article 组件拥有 hits 状态变量。为了使用状态,我们需要将其转换为客户端组件:
你看到这个问题了吗?当 Article 重新渲染时,它的子组件也会重新渲染,包括 HitCounter 和 Discussion 。然而,如果这些是服务器组件,它们无法重新渲染。
为了防止这种不可能的情况发生,React 团队添加了一条规则:客户端组件只能导入其他客户端组件。这意味着这些 HitCounter 和 Discussion 的实例需要成为客户端组件。
React Server Components 新的范式完全是关于创建客户端边界。实际上,以下是最终发生的情况:
当我们向 'use client' 组件添加指令时,我们创建了一个"客户端边界"。在这个边界内的所有组件都会隐式转换为客户端组件。即使像 HitCounter 这样的组件没有 'use client' 指令,在这种特定情况下它们仍然会在客户端上进行水合/渲染。
这意味着我们不需要在每个需要在客户端运行的文件中都添加 'use client' 。实际上,我们只需要在创建新的客户端边界时添加它。
Workarounds 解决方法
当我第一次得知客户端组件无法渲染服务器端组件时,我感到相当受限制。如果我需要在应用程序的高层使用状态,那么是不是意味着所有的东西都需要变成客户端组件呢?
事实证明,在许多情况下,我们可以通过重新构建应用程序的所有者来解决这个限制。
这是一个棘手的问题,所以让我们用一个例子来解释一下:
jsx
'use client';
import { DARK_COLORS, LIGHT_COLORS } from '@/constants.js';
import Header from './Header';
import MainContent from './MainContent';
function Homepage() {
const [colorTheme, setColorTheme] = React.useState('light');
const colorVariables = colorTheme === 'light'
? LIGHT_COLORS
: DARK_COLORS;
return (
<body style={colorVariables}>
<Header />
<MainContent />
</body>
);
}
在这个设置中,我们需要使用 React 状态来允许用户在暗模式/亮模式之间切换。这需要在应用程序树的高层进行,以便我们可以将 CSS 变量标识应用于 <body>
标签。
为了使用状态,我们需要将 Homepage 设置为客户端组件。由于这是我们应用程序的顶部,这意味着所有其他组件 - Header 和 MainContent - 也将隐式成为客户端组件。
为了解决这个问题,让我们将颜色管理的内容提取到它自己的组件中,并将其移动到自己的文件中:
jsx
// /components/ColorProvider.js
'use client';
import { DARK_COLORS, LIGHT_COLORS } from '@/constants.js';
function ColorProvider({ children }) {
const [colorTheme, setColorTheme] = React.useState('light');
const colorVariables = colorTheme === 'light'
? LIGHT_COLORS
: DARK_COLORS;
return (
<body style={colorVariables}>
{children}
</body>
);
}
回到 Homepage ,我们可以这样使用这个新组件:
jsx
// /components/Homepage.js
import Header from './Header';
import MainContent from './MainContent';
import ColorProvider from './ColorProvider';
function Homepage() {
return (
<ColorProvider>
<Header />
<MainContent />
</ColorProvider>
);
}
我们可以从 Homepage 中删除 'use client' 指令,因为它不再使用状态或任何其他客户端 React 功能。这意味着 Header 和 MainContent 将不再被隐式转换为客户端组件!
但等一下。作为一个客户端组件, ColorProvider 是 Header 和 MainContent 的父级。无论如何,它仍然在树中更高层次。
说到客户端边界,父子关系并不重要。 Homepage 是导入和渲染 Header 和 MainContent 的那个。这意味着 Homepage 决定了这些组件的属性是什么。
记住,我们要解决的问题是服务器组件无法重新渲染,因此无法为其任何 props 提供新值。通过这种新的设置, Homepage 决定了 Header 和 MainContent 的属性,而且由于 Homepage 是一个服务器组件,所以没有问题。
更准确地说, 'use client' 指令适用于文件/模块级别。在客户端组件文件中导入的任何模块都必须是客户端组件。当打包程序打包我们的代码时,它将按照这些导入进行操作!
更改颜色主题? 在我上面的例子中,你可能已经注意到没有办法改变颜色主题。 setColorTheme 从未被调用。 我希望尽可能地保持简洁,所以我省略了一些东西。一个完整的示例将使用 React 上下文使 setter 函数可用于任何后代组件。只要消费上下文的组件是客户端组件,一切都运行良好!
Peeking under the hood 窥探内部机制
让我们从一个较低的层次来看这个问题。当我们使用服务器组件时,输出是什么样的?实际上生成了什么?
让我们从一个超级简单的 React 应用开始:
jsx
function Homepage() {
return (
<p>
Hello world!
</p>
);
}
在 React Server Components 范式中,默认情况下,所有组件都是服务器组件。由于我们没有明确将此组件标记为客户端组件(或在客户端边界内渲染它),它只会在服务器上进行渲染。
当我们在浏览器中访问此应用程序时,我们将收到一个类似以下的 HTML 文档:
html
<!DOCTYPE html>
<html>
<body>
<p>Hello world!</p>
<script src="/static/js/bundle.js"></script>
<script>
self.__next['$Homepage-1'] = {
type: 'p',
props: null,
children: "Hello world!",
};
</script>
</body>
</html>
Some liberties taken 有些自由的处理方式 为了更容易理解,我已经在这里重新组织了一些内容。例如,在 RSC 上下文中生成的真正的 JS 使用了字符串化的 JSON 数组,这是为了优化减小 HTML 文档的文件大小。 我还删除了 HTML 中所有非关键部分(比如
<head>
)。
我们可以看到,我们的 HTML 文档包含了由 React 应用程序生成的 UI,即 "Hello world!" 段落。这要归功于服务器端渲染,并不直接归因于 React 服务器组件。
下面是一个加载我们 JS 捆绑包的 <script>
标签。该捆绑包包括像 React 这样的依赖项,以及我们应用程序中使用的任何客户端组件。由于我们的 Homepage 组件是一个服务器组件,该组件的代码不包含在此捆绑包中。
最后,我们有一个带有一些内联 JS 的第二个 <script>
标签:
javascript
self.__next['$Homepage-1'] = {
type: 'p',
props: null,
children: "Hello world!",
};
这是非常有趣的部分。我们在这里告诉 React:"嘿,我知道你缺少 Homepage 组件的代码,但别担心:这是它渲染的内容"。
通常情况下,当 React 在客户端进行注水时,它会快速渲染所有组件,构建应用程序的虚拟表示。但对于服务器组件来说,它无法这样做,因为代码不包含在 JS 捆绑包中。
因此,我们将生成的虚拟表示一并发送给客户端。当 React 在客户端加载时,它会重用该描述,而不是重新生成它。
这就是为什么上面的示例能够正常工作的原因。来自 ColorProvider 和 Header 的输出通过 children 属性传递给 ColorProvider 组件。无论 ColorProvider 如何重新渲染,这些数据都是静态的,由服务器锁定。
如果你好奇想看到服务器组件如何序列化并通过网络发送的真实表示,请查看开发者 Alvar Lagerlöf 的 RSC Devtools.
服务器组件不需要服务器 在本文的前面,我提到了服务器端渲染是一个"总称",包括许多不同的渲染策略,包括:
- 静态:HTML 在应用程序构建过程中生成,在部署过程中。
- 动态:HTML是"按需"生成的,当用户请求页面时。
React Server Components 与这两种渲染策略兼容。当我们的服务器组件在 Node.js 运行时渲染时,它们返回的 JavaScript 对象将被创建。这可以在按需或构建过程中发生。
这意味着可以在没有服务器的情况下使用 React 服务器组件!我们可以生成一堆静态 HTML 文件,并将它们托管在任何地方。 事实上,在 Next.js 应用程序路由器中,默认情况下就是这样。除非我们真的需要"按需"发生的事情,否则所有这些工作都会在构建过程中提前完成。
完全没有 React? 你可能会想:如果我们的应用程序中不包含任何客户端组件,我们是否真的需要下载 React?我们能否使用 React 服务器组件来构建一个真正静态的无 JS 网站? 问题是,React Server Components 仅在 Next.js 框架中可用,而该框架有一堆需要在客户端上运行的代码,用于管理诸如路由之类的事物。 然而,出人意料的是,这实际上会产生更好的用户体验;例如,Next.js 的路由器将比典型的<a>
标签更快地处理链接点击,因为它不需要加载一个全新的 HTML 文档。 一个结构良好的 Next.js 应用程序在 JS 下载时仍然可以正常工作,但一旦 JS 加载完成,它将变得更快/更好。
Advantages 优势
React Server Components 是在 React 中运行仅限服务器代码的第一个"官方"方式。正如我之前提到的,然而,在更广泛的 React 生态系统中,这并不是一件新鲜事;自 2016 年以来,我们就能够在 Next.js 中运行仅限服务器代码!
最大的区别在于我们以前从未有过一种方法在组件内运行仅限于服务器的代码。
最明显的好处是性能。服务器组件不会被包含在我们的JS包中,这减少了需要下载的 JavaScript 数量,以及需要进行渲染的组件数量。
老实说,大多数 Next.js 应用在"页面交互"时间方面已经足够快了。
如果你遵循语义化 HTML 的原则,大部分应用在 React 完成注水之前就已经可以正常工作了。链接可以被点击,表单可以被提交,手风琴可以展开和折叠(使用 <details>
和 <summary>
)。对于大多数项目来说,React 完成注水需要几秒钟也是可以接受的。
但是有一件事我觉得非常酷:我们再也不需要在功能和捆绑包大小之间做出同样的妥协了!
例如,大多数技术博客都需要某种语法高亮库。在这个博客上,我使用的是 Prism。代码片段看起来像这样:
一个合适的语法高亮库,支持所有流行的编程语言,将会有几兆字节大小,远远超过JS捆绑包的容量。因此,我们不得不做出妥协,剔除那些非关键的语言和功能。
但是,假设我们在服务器组件中进行语法高亮。在这种情况下,库代码实际上不会包含在我们的 JS 捆绑包中。因此,我们不需要做任何妥协,可以使用所有的花哨功能。
这就是 Bright 背后的大思路,它是一个现代的语法高亮包,专为与 React Server Components 配合使用而设计。
这就是让我对 React 服务器组件感到兴奋的原因。那些在 JS 捆绑包中成本过高而无法包含的东西现在可以在服务器上免费运行,不会增加我们的捆绑包大小,从而提供更好的用户体验。
这不仅仅是关于性能和用户体验。在使用 RSC 一段时间后,我真的很欣赏服务器组件的简单易用。我们再也不用担心依赖数组、过时的闭包、记忆化或其他由变化引起的复杂问题。
最终,现在还只是刚刚开始的阶段。React Server Components 才在几个月前从测试版中出现!我非常期待在接下来的几年里看到事物的发展,因为社区将继续创新像 Bright 这样的新解决方案,充分利用这种新的范式。作为 React 开发者,现在是一个令人兴奋的时刻!