导言
有一件事让我觉得自己老了:React 今年庆祝了它的十岁生日!
自从 React 首次面向一群困惑的开发者社区推出以来的十年里,它经历了几次演进。React 团队在涉及根本性变化时一直不吝于行动:如果他们发现了更好的解决方案,他们会毫不犹豫地采用。
几个月前,React 团队推出了 React Server Components,这是最新的范式转变。这是有史以来第一次,React 组件可以完全在服务器上运行。
关于这个问题,在线上存在很多混乱。很多人对这是什么、它是如何工作的、好处是什么,以及它如何与服务器端渲染等其他内容如何结合存在很多问题。
我一直在进行大量的 React Server Components 实验,并回答了很多我自己的问题。我必须承认,我对这些东西比我预期的要激动得多。它真的很酷!
所以,我的目标今天是帮助你解开 React Server Components 的神秘面纱,回答你可能对 React Server Components 有的很多问题!
目标受众: 这篇教程主要面向已经在使用React的开发者,对React Server Components感到好奇。你不需要成为React专家,但如果你刚刚开始学习React,可能会感到相当困惑。
服务器端渲染快速入门
为了将 React Server Components 置于背景中,了解服务器端渲染(SSR)的工作原理会有所帮助。如果您已经熟悉了服务器端渲染,可以随时跳到下一个标题!
当我在2015年开始使用 React 时,大多数 React 设置都使用了"客户端"渲染策略。用户会收到一个类似于以下的 HTML 文件:
xml
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
</body>
</html>
这个 bundle.js 脚本包含了我们运行应用所需的一切,包括 React、其他第三方依赖项以及我们编写的所有代码。
一旦 JavaScript 被下载并解析,React 就会开始工作,创建整个应用程序的所有 DOM 节点,并将其放入那个空的
中。
这种方法的问题在于,执行所有这些工作需要时间。而在所有这些工作进行的时候,用户只能看到一个空白的白屏。这个问题随着时间的推移而变得更加严重:我们发布的每个新功能都会增加 JavaScript 捆绑包的大小,延长用户等待的时间。
服务器端渲染旨在改善这种体验。服务器不再发送空的 HTML 文件,而是渲染我们的应用程序以生成实际的 HTML。用户会收到一个完整的 HTML 文档。
这个 HTML 文件仍然会包含 <script>
标签,因为我们仍然需要 React 在客户端上运行,以处理任何交互。但是我们会在浏览器中配置 React 以稍微不同的方式工作:它不再从头开始创建所有的 DOM 节点,而是采用现有的 HTML。这个过程称为"hydration"。
我喜欢 React 核心团队成员 Dan Abramov
对此的解释方式:
"Hydration(水合)就像用交互性和事件处理程序的"水"来滋润"干燥"的 HTML。
一旦 JavaScript 捆绑包被下载,React 将迅速遍历整个应用程序,构建 UI 的虚拟草图,并将其"贴合"到真实的 DOM 上,附加事件处理程序,触发任何效果等等。
这就是服务器端渲染(SSR)的要点。服务器生成初始的 HTML,这样用户在下载和解析 JavaScript 捆绑包的过程中就不必盯着空白的页面。客户端 React 接着服务器端 React 的工作,采用 DOM 并添加交互性。
总括
当我们谈论服务器端渲染时,通常会想象一个如下所示的流程:
1.用户访问 myWebsite.com。
2.一个 Node.js 服务器接收到请求,并立即渲染 React 应用程序,生成 HTML。
3.新鲜出炉的 HTML 发送给客户端。 这是一种实现服务器端渲染的可能方式,但不是唯一的方式。另一种选择是在构建应用程序时生成 HTML。
通常,React 应用程序需要进行编译,将 JSX 转换为普通的 JavaScript,并捆绑所有的模块。如果在同一个过程中,我们"预渲染"了所有不同路由的 HTML 呢?
这通常被称为静态站点生成(SSG)。它是服务器端渲染的一个子变种。
在我看来,"服务器端渲染"是一个总称,包括几种不同的渲染策略。它们都有一个共同点:初始渲染发生在像 Node.js 这样的服务器运行时中,使用 ReactDOMServer API。实际上,这发生的时间并不重要,无论是按需还是在编译时。不管怎样,都属于服务器端渲染。
来回反弹
让我们来谈谈在 React 中的数据获取。通常情况下,我们有两个分开的应用程序,它们通过网络进行通信:
- 客户端React应用
- 服务器端REST API
使用像 React Query、SWR 或 Apollo 这样的工具,客户端会发起一个网络请求到后端,后端然后从数据库获取数据并通过网络发送回客户端。
我们可以使用图表来可视化这个流程:
关于图表的说明:
这篇博客文章包括了多个"网络请求图表"。它们的设计目的是可视化数据在不同的渲染策略下,从客户端(浏览器)到服务器(后端API)之间的流动方式。
底部的数字代表了一个虚构的时间单位。它们不是分钟或秒。实际上,这些数字根据许多不同的因素会有很大的变化。这些图表旨在让您对概念有一个高层次的理解,它们并不模拟任何真实的数据。
上面的图表展示了使用客户端端渲染(CSR)策略的流程。它始于客户端接收到一个HTML文件。这个文件没有任何内容,但包含一个或多个 <script>
标签。
一旦 JavaScript 被下载并解析,我们的 React 应用程序将启动,创建一堆 DOM 节点并填充UI。不过,一开始,我们还没有实际的数据,所以我们只能使用加载状态来渲染外壳(头部、底部、一般布局)。
您可能经常看到这种模式。例如,UberEats 首先渲染外壳,同时获取它需要填充实际餐厅信息的数据:
用户将在网络请求解决并React重新渲染后才会看到这个加载状态,用真实内容替换加载UI。
让我们看看我们可以采用的另一种架构方式。下一个图表保持了相同的一般数据获取模式,但使用服务器端渲染而不是客户端渲染:
在这个新的流程中,我们在服务器上执行了第一次渲染。这意味着用户会收到一个不是完全空白的HTML文件。
这是一个改进 - 一个外壳比一个空白的白屏好 - 但最终,它并没有在显著的方式上改变用户体验。用户访问我们的应用程序不是为了看到加载屏幕,而是为了查看内容(餐厅、酒店列表、搜索结果、消息等)。
为了真正了解用户体验的差异,让我们在图表中添加一些Web性能指标。在这两种流程之间切换,注意标志的变化:
每个标志代表一个常用的Web性能指标。以下是详细说明:
- 首次绘制(First Paint) - 用户不再盯着一个空白的白屏。一般的布局已经渲染完成,但内容仍然缺失。有时称为FCP(首次内容绘制)。
- 页面可交互(Page Interactive) - React已经被下载,我们的应用程序已经被渲染/水合。交互元素现在完全响应。有时称为TTI(可交互时间)。
- 内容绘制(Content Paint) - 页面现在包括用户关心的内容。我们已经从数据库中获取了数据并在UI中进行了渲染。有时称为LCP(最大内容绘制)。
通过在服务器上进行初始渲染,我们能够更快地绘制初始的"外壳"。这可以使加载体验感觉更快一些,因为它提供了一种进展的感觉,表明事情正在发生。
在某些情况下,这将是一个有意义的改进。例如,也许用户只是在等待头部加载完成,以便他们可以点击导航链接。
但是,这个流程感觉有点愚蠢吗?当我看到SSR图时,我不禁注意到请求是在服务器上启动的。为什么不在初始请求期间完成数据库工作,而不需要进行第二次往返的网络请求呢?
换句话说,为什么不像这样做呢?
不再在客户端和服务器之间来回跳转,我们在初始请求的一部分中执行数据库查询,将完全填充的UI直接发送给用户。
但是,嗯,我们要如何做到这一点呢?
为了使这项工作生效,我们需要能够为React提供一个代码块,它仅在服务器上运行,以执行数据库查询。但在React中,这一直都不是一个选项...即使使用服务器端渲染,我们所有的组件都会在服务器和客户端都进行渲染。
生态系统已经为这个问题提出了很多解决方案。类似 Next.js 和 Gatsby 这样的元框架已经创建了自己的方式来仅在服务器上运行代码。
例如,这是使用Next.js(使用传统的"页面"路由器)的示例:
javascript
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
对象。然后,这些 props
被传递到组件中,在服务器上首先进行渲染,然后在客户端进行水合处理。
这里的聪明之处在于,getServerSideProps
不会在客户端重新运行。事实上,这个函数甚至不会包含在我们的JavaScript捆绑包中!
这种方法在当时是非常领先的。老实说,它相当不错。但是它也有一些缺点:
- 这种策略仅在路由级别,对于树的顶层组件有效。我们不能在任何组件中使用这种方法。
- 每个元框架都提出了自己的方法。Next.js 有一种方法,Gatsby 有另一种方法,Remix 有另一种方法。这并没有被标准化。
- 所有的 React 组件将始终在客户端上进行水合处理,即使没有必要这样做。
多年来,React 团队一直在默默地研究这个问题,试图提出官方解决方案。他们的解决方案被称为 React Server Components。
React服务器组件简介
在高层次上,React Server Components 是一个全新范式的名称。在这个新世界中,我们可以创建仅在服务器上运行的组件。这使我们能够在React组件内部直接编写数据库查询!
这里是一个"服务器组件"的快速示例:
javascript
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的人,起初看到这段代码时感到非常奇怪。 😅
但是,等等!我的直觉喊道:"函数组件不能是异步的!我们也不允许在渲染中直接进行副作用!"
要理解的关键事情是:服务器组件永远不会重新渲染。它们在服务器上运行一次以生成UI。渲染后的值被发送到客户端并锁定在原位。就React而言,这个输出是不可变的,永远不会改变。*
这意味着React的API中的大部分部分与服务器组件不兼容。例如,我们不能使用状态,因为状态可以更改,但服务器组件不能重新渲染。我们也不能使用效果,因为效果只在渲染之后,在客户端上运行,而服务器组件永远不会到达客户端。
这也意味着在规则方面我们有更多的灵活性。例如,在传统的React中,我们需要将副作用放在useEffect回调或事件处理程序中,以便它们不会在每次渲染时重复执行。但是如果组件只运行一次,我们就不必担心这个问题!
服务器组件本身非常简单,但"React Server Components"范式要复杂得多。这是因为我们仍然有常规的组件,它们如何组合在一起可能相当令人困惑。
在这个新的范式中,我们熟悉的"传统"React组件被称为客户端组件。老实说,我不太喜欢这个名字。 😅
"客户端组件"的名称暗示着这些组件只在客户端上渲染,但实际上并非如此。客户端组件在客户端和服务器上都进行渲染。
我知道所有这些术语可能相当令人困惑,所以这是我如何总结它们的方式:
-
React Server Components 是这个新范式的名称。
-
在这个新范式中,我们熟悉和喜爱的"标准"React组件已经被重新定义为客户端组件。这是一个新的名称,用于表示旧事物。
-
这个新范式引入了一种新类型的组件,即服务器组件。这些新组件专门在服务器上渲染。它们的代码不包含在JavaScript捆绑包中,因此它们不会进行水合处理或重新渲染。
让我们澄清另一个常见的混淆点:React Server Components 不是服务器端渲染(SSR)的替代品。您不应该将React Server Components视为"SSR版本2.0"。
相反,我更愿意将其视为两个完美契合的独立拼图,两种互补的方式。
我们仍然依赖服务器端渲染来生成初始的HTML。React Server Components 在此基础上构建,允许我们从客户端JavaScript捆绑包中省略某些组件,确保它们仅在服务器上运行。
事实上,甚至可以在没有服务器端渲染的情况下使用React Server Components,尽管在实际应用中,如果将它们一起使用,您将获得更好的结果。React团队构建了一个没有SSR的最小RSC演示,如果您想看一个示例,可以查看它。
兼容环境
通常情况下,当出现新的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的框架;我计划不时检查这个页面,看看是否有新的选择可用。
指定客户端组件
在这个新的"React Server Components"范式中,默认情况下假定所有组件都是服务器组件。我们必须通过"选择加入"来定义客户端组件。
我们通过指定一个全新的指令来实现这一点:
javascript
'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 Server Components范式中,组件默认被视为服务器组件。事实上, 'use server' 用于服务器操作,这是一个完全不同的功能,超出了本博客文章的范围。
您可能会想知道:我应该如何决定特定组件应该是服务器组件还是客户端组件?
通常情况下,如果一个组件可以是服务器组件,那么它应该是服务器组件。服务器组件通常更简单,更容易理解。还有一个性能优势:因为服务器组件不在客户端上运行,它们的代码不包含在我们的JavaScript捆绑包中。React Server Components范式的一个好处是它有潜力改善页面交互(TTI)指标。
也就是说,我们也不应该使自己的任务成为尽可能消除尽可能多的客户端组件!我们不应该试图优化最小数量的客户端组件。值得记住的是,直到现在,每个React应用中的每个React组件都是客户端组件。
当您开始使用React Server Components时,您可能会发现这相当直观。我们的一些组件需要在客户端上运行,因为它们使用状态变量或效果。对于这些组件,您可以添加
'use client'
指令。否则,您可以将它们保留为服务器组件。
边界
当我开始熟悉React Server Components时,我提出的第一个问题之一是:当props发生变化时会发生什么?
例如,假设我们有一个这样的服务器组件:
javascript
function HitCounter({ hits }) {
return (
<div>
Number of hits: {hits}
</div>
);
}
假设在初始的服务器端渲染中,hits
等于 0。那么,这个组件将产生以下标记:
html
<div>
Number of hits: 0
</div>
但是,如果 hits 的值发生变化会发生什么呢?假设它是一个状态变量,并且从0变为1。HitCounter需要重新渲染,但它不能重新渲染,因为它是一个服务器组件!
问题是,单独看服务器组件并没有太多意义。我们必须放大视野,以更全面的方式来考虑我们应用程序的结构。
假设我们有以下组件树:
如果所有这些组件都是服务器组件,那么一切都是合理的。因为没有一个组件会重新渲染,所以没有一个props会发生变化。
但是假设Article组件拥有 hits 状态变量。为了使用状态,我们需要将其转换为客户端组件:
你看到问题了吗?当Article重新渲染时,所有拥有的组件也将重新渲染,包括HitCounter和Discussion。但是,如果这些都是服务器组件,它们不能重新渲染。
为了防止这种不可能的情况发生,React团队添加了一条规则:客户端组件只能导入其他客户端组件。'use client' 指令意味着这些HitCounter和Discussion的实例将需要变成客户端组件。
使用React Server Components时,我最大的"噢,原来如此"的时刻之一就是意识到这个新范式实际上是关于创建客户端边界。以下是实际发生的情况:
当我们将 'use client' 指令添加到Article组件时,我们创建了一个"客户端边界"。在这个边界内的所有组件都会被隐式转换为客户端组件。即使像HitCounter这样的组件没有 'use client' 指令,在这种特定情况下它们仍然会在客户端上进行水合处理/渲染。
这意味着我们不必在每个需要在客户端上运行的文件中添加 'use client'。实际上,我们只需要在创建新的客户端边界时添加它。
变通
当我第一次了解到客户端组件不能渲染服务器组件时,我觉得这相当受限制。如果我需要在应用程序的高层级别使用状态怎么办?这是否意味着一切都必须变成客户端组件?
事实证明,在许多情况下,我们可以通过重新构建应用程序以改变所有者来解决这个限制。
这是一个难以解释的复杂问题,所以让我们使用一个示例来说明:
javascript
'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变量标记应用于 标签。
为了使用状态,我们需要使Homepage成为客户端组件。由于这是我们应用程序的顶部,这意味着所有其他组件,包括Header和MainContent,也将隐式成为客户端组件。
为了解决这个问题,让我们将颜色管理相关的内容提取到自己的组件中,并将其移动到自己的文件中:
javascript
// /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,我们像这样使用这个新组件:
javascript
// /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是什么。
请记住,我们试图解决的问题是服务器组件无法重新渲染,因此无法为其任何props提供新值。在这个新的设置中,Homepage决定了Header和MainContent的props是什么,而且由于Homepage是服务器组件,所以没有问题。
这是一个非常令人费解的问题。即使在多年的React经验之后,我仍然觉得这非常令人困惑 😅。需要进行相当多的实践才能对此产生直观感觉。
更准确地说, 'use client' 指令在文件/模块级别工作。在客户端组件文件中导入的任何模块也必须是客户端组件。当bundler捆绑我们的代码时,它毕竟会遵循这些导入!
改变颜色主题?
在我上面的例子中,你可能已经注意到没有办法改变颜色主题。 setColorTheme 从未被调用。
我想让事情尽可能地少,所以我留下了一些东西。一个完整的例子将使用React上下文来使setter函数可用于任何后代。只要使用上下文的组件是客户端组件,一切都工作得很好!
窥视底层实现
让我们从更低的层次来看这个问题。当我们使用服务器组件时,输出是什么样子的?实际上产生了什么?
让我们从一个超级简单的React应用程序开始:
javascript
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>
为了更容易理解,我在这里采取了一些自由重新构建的措施。例如,在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和MainContent输出的内容通过children属性传递给ColorProvider组件。ColorProvider可以重新渲染多少次都可以,但这些数据是静态的,由服务器锁定。
如果你想看到服务器组件是如何序列化并发送到网络上的真实表示形式,请查看开发者Alvar Lagerlöf的RSC Devtools。
服务器组件不需要服务器
在本文的前面部分,我提到服务器端渲染是一个"大伞术语",涵盖了许多不同的渲染策略,包括:
静态:HTML在构建应用程序期间,即在部署过程中生成。
动态:HTML在用户请求页面时"按需"生成。
React Server Components与这两种渲染策略兼容。当我们的服务器组件在Node.js运行时中渲染时,它们返回的JavaScript对象将被创建。这可以在按需或构建过程中发生。
这意味着可以在没有服务器的情况下使用React Server Components!我们可以生成一堆静态HTML文件,并将它们托管在任何地方。事实上,在Next.js App Router中默认就是这样的。除非我们真的需要"按需"执行某些操作,否则所有这些工作都会在构建过程中提前完成。
完全不使用React?你可能会想:如果我们的应用程序中没有包含任何客户端组件,那么我们是否真的需要下载React?我们是否可以使用React Server Components来构建一个真正的静态无JS网站?
问题是,React Server Components仅在Next.js框架内可用,而该框架有一堆需要在客户端运行的代码,用于管理路由等事务。
然而,出奇地,这实际上往往会产生更好的用户体验;例如,Next.js的路由器会比典型的
<a>
标签更快地处理链接点击,因为它不必加载一个全新的HTML文档。一个良好结构的Next.js应用程序在JS下载时仍然可以工作,但一旦JS加载,它会变得更快/更好。
优势
React Server Components是在React中运行服务器专用代码的第一个"官方"方式。正如我之前提到的,尽管在更广泛的React生态系统中,这并不是一个新的事物;自2016年以来,我们一直可以在Next.js中运行服务器专用代码!
最大的区别在于,以前我们从未有过在组件内运行服务器专用代码的方式。
最明显的好处是性能。服务器组件不包含在我们的JS捆绑包中,这减少了需要下载的JavaScript数量,以及需要水化的组件数量:
不过,这对我来说可能是最不令人兴奋的事情。老实说,大多数Next.js应用程序在"页面交互"时序方面已经足够快了。
如果遵循语义HTML原则,大部分应用程序在React水合之前应该可以正常工作。链接可以被点击,表单可以被提交,手风琴可以展开和折叠(使用
和)。对于大多数项目来说,如果React需要几秒钟来水合也是可以接受的。
但这里有一件我认为非常酷的事情:我们不再需要在功能与捆绑大小之间做出相同的妥协!
例如,大多数技术博客都需要某种语法高亮库。在这个博客上,我使用Prism。代码片段看起来像这样:
javscript
function exampleJavaScriptFunction(param) {
return "Hello world!"
}
一个真正的语法高亮库,支持所有流行的编程语言,大小将会达到数兆字节,远远超过了可以放在JS捆绑包中的大小。因此,我们必须做出妥协,删除那些不是使命关键的语言和功能。
但是,假设我们在一个服务器组件中进行语法高亮。在这种情况下,库代码实际上不会包含在我们的JS捆绑包中。因此,我们不需要做任何妥协,可以使用所有的功能。
这就是Bright的核心思想,它是一个现代的语法高亮包,设计用于与React Server Components一起使用。
这是让我对React Server Components感到兴奋的事情。那些在JS捆绑包中成本太高的东西现在可以免费在服务器上运行,不会增加我们捆绑包的大小,同时提供更好的用户体验。
这也不仅仅是关于性能和用户体验。在使用RSC一段时间后,我真的很喜欢Server Components的简单易用。我们永远不必担心依赖数组、过期闭包、记忆化或任何其他由于事物发生变化而引起的复杂问题。
最终,这还只是一个非常早期的阶段。React Server Components才在几个月前退出了beta版!我真的很期待在未来几年里看到事物如何发展,因为社区将继续创新像Bright这样的新解决方案,利用这个新范例。对于React开发人员来说,这是一个令人兴奋的时刻!