简介
亲爱的,React 诞生十年了,这让我感到惊奇和欣慰。在这十年里,React 经历了几次重大的升级迭代。React 团队 一直在尝试新的解决方案。比如,他们最近发布了 React 服务器组件,这是一次巨大的变革。服务器组件可以完全 运行在服务器端。
网上对这一新特性有很多困惑和疑问。我尝试了服务器组件,并解答了许多自己的疑问。我对它越来越激动了。它 真的很酷!
我今天的目标是帮助大家消除关于 React 服务器组件的疑惑,回答你们可能有的问题。
本教程主要针对已经在使用 React 的开发者,特别是那些对 React 服务器组件感兴趣的人。你不需要是 React 专 家,但如果你才刚刚开始学习 React,可能会觉得比较困惑。
关于服务端渲染的简单介绍
为了理解 React 服务端组件,首先需要了解服务端渲染(SSR)的工作原理。如果你已经熟悉 SSR,可以直接跳到下一 节!
当我 2015 年刚开始使用 React 时,大多数 React 应用采用“客户端渲染”策略。用户会收到一个像这样的 HTML 文件:
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<script src="/static/js/bundle.js"></script>
</body>
</html>
这个 bundle.js 脚本包含了我们需要的所有代码,包括 React、第三方依赖和我们编写的代码。
加载并解析 JS 后,React 会立即开始工作,在空的 <div id="root">
中生成整个应用的 DOM 节点。
这个方法的问题是需要时间来完成所有工作。而在此期间,用户会面对一个空白页面。随着我们不断添加新特 性,这个问题会越来越严重,因为 JS bundle 会变得越来越大,加载和解析的时间也越长。
服务端渲染就是为了改善这一体验而设计的。服务器不再发送空白 HTML ,而是渲染我们的应用生成实际的 HTML。 这样用户可以立即看到完整的页面。
生成的 HTML 仍会包含 <script>
标签,因为我们仍需要 React 在浏览器端运行,来处理交互。但是我们会以稍
微不同的方式配置浏览器中的 React:它不再从头开始生成所有 DOM 节点,而是 采用 已存在的 HTML。这个过
程称为水化。
React 核心团队成员 Dan Abramov 是这样解释水化的:
水化就像用互动和事件处理程序的“水”浇灌“干燥”的 HTML。
一旦 JS bundle 下载完成,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。渲染何时发生并不重要,无论是按需还是在编译时都算是服务端渲染。
客户端与服务端的往返
让我们来讨论 React 中的数据获取。 通常,我们会有两个相互通过网络进行通信的应用:客户端 React 应用服务端 REST API 使用像 React Query、SWR 或 Apollo 这样的库,客户端会发起网络请求到后端,后端再从数据库获取数据并通过网络发回。
我们可以用一个图来可视化这个流程:
本文包含几个“网络请求图”,目的是可视化不同渲染策略下,数据在客户端(浏览器)和服务端(后端 API)之间的传递。
底部的数字代表一个假想的时间单位,并不是分钟或秒。实际的数字会根据许多不同因素而大相径庭。 这些图旨在帮助你从高层次理解相关概念,并不是在模拟任何真实数据。
主要关注数据流动的 overall 方向和 client 与 server 之间的往返次数。具体的时间数值并不重要,仅用于说明的目的。
这个第一个图展示了使用客户端渲染(CSR)策略的流程。它从客户端收到一个 HTML 文件开始。这个文件没有任何内容,只包含一个或多个 <script>
标签。
一旦 JS 下载并解析后,我们的 React 应用会启动,生成一堆 DOM 节点并填充 UI。但一开始,我们还没有任何实际的数据,所以只能渲染布局(头部、底部、总体布局)和加载状态。
你可能经常见过这种模式。例如,Uber Eats 在获取需要的数据渲染实际餐厅之前,会先渲染布局:
用户会看到这个加载状态,直到网络请求完成,React 用真实内容重新渲染为止。
让我们看另一种架构方式。 这个图保持了相同的数据获取模式,但使用了服务端渲染而不是客户端渲染:
在这个新的流程中,我们在服务器上执行首次渲染。这意味着用户收到的 HTML 文件不再是完全空白的。
这是一个改进 - 相比空白页,shell 体验更好 - 但从整体来看,它并没有带来非常明显的提升。用户访问我们的应用不是为了看加载界面,而是为了查看内容(餐厅、酒店、搜索结果、消息等等)。
为了真正理解用户体验上的差异,我们在图中添加一些网页性能指标。在这两种流程中切换,并留意指标的变化:
这些标志代表了一些常用的网页性能指标。具体如下:
首次绘制 - 用户不再盯着空白屏幕。总体布局已经渲染出来,但内容还缺失。这有时也称为 FCP(首次内容绘制)。
页面可交互 - React 已下载,应用已经渲染/水化。可交互元素现在可以完全响应。这有时也称为 TTI(可交互时间)。
内容绘制 - 页面现在包含用户关心的内容。我们从数据库拉取数据并在 UI 中渲染出来。这有时也称为 LCP(最大内容绘制)。
通过在服务器上进行初始渲染,我们可以更快地绘制那个初始“shell”。这可以使加载体验感觉更快,因为它提供了进度的感觉,似乎东西在发生。
在某些情况下,这确实会是一个明显的改进。例如,用户可能只是等待头部加载以便点击一个导航链接。
但这种流程感觉有点傻,不是吗? 当我看SSR图时,我不禁注意到请求是从服务器上开始的。与需要第二次网络往返请求不同,为什么我们不在那个初始请求期间进行数据库工作呢?
换句话说,为什么不这样做呢?
通过在初始请求中获取数据,我们可以避免客户端需要进行第二次请求。这不仅改善了性能,还简化了客户端的逻辑。
总体来说,能在一个请求中完成服务端渲染和数据获取是更好的方案。这正是React服务端组件希望解决的问题。
与其客户端与服务端来回跳转,不如我们在初始请求中进行数据库查询,直接将填充了数据的 UI 发送给用户。
但问题是,我们要如何具体实现呢?
为了让这种方式可行,我们需要能够给 React 一段仅在服务端运行的代码,用来进行数据库查询。但在 React 中一直没有这样的选项......即使使用服务端渲染,我们的所有组件也会在服务端和客户端都渲染。
为解决这个问题,生态系统提出了许多方案。 一些元框架,比如 Next.js 和 Gatsby 创造了自己的方式在服务端执行代码。
例如,使用 Next.js(传统的 “页面” 路由)看起来像这样:
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 服务端组件。
React 服务器组件简介
从高层次来看,React 服务端组件 是一种全新的范例。在这种新方案中,我们可以创建只在服务端运行的组件。这使我们可以在 React 组件内直接编写数据库查询等操作!
这样就无需像以前那样在服务端和客户端编写重复的渲染逻辑。我们可以用同一组件同时处理服务端渲染和数据获取。
React 服务端组件为构建真正的同构 React 应用提供了基础。这是 React 在可扩展性和性能上的一次突破。
这是“服务器组件”的一个简单示例:
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 与服务端组件不兼容。例如,我们不能使用 state,因为 state 可以改变,但服务端组件无法重新渲染。我们也不能使用 effects,因为 effects 只会在渲染之后运行,在客户端上,而服务端组件永远不会到达客户端。
这也意味着我们在规则上有更多灵活性。例如,在传统 React 中,我们需要把副作用放进 useEffect
回调或事件处理函数中,以防它们在每次渲染时重复执行。但是如果组件只运行一次,我们就不需要担心这个问题!
服务端组件本身非常简单,但“React 服务端组件”范式要复杂得多。这是因为我们仍然有通常的组件,它们的组合方式可能会让人困惑。
在这种新范式中,我们熟悉的“传统” React 组件称为客户端组件。坦白说,我不太喜欢这个名称。😅
“客户端组件”这个名称意味着这些组件只会在客户端渲染,但实际上不是这样。客户端组件会在客户端和服务端都渲染
我知道所有的这些术语都很容易让人困惑,所以我来概括一下:
- React 服务端组件 是这种新范式的名字。
- 在这种新范式中,我们熟知和喜爱的“标准” React 组件被重新命名为客户端组件。这是旧事物的新名字。
- 这个新范式引入了一种新的组件类型,服务端组件。这些新组件仅在服务端渲染。它们的代码不会被打包到 JS 中,也不会水化或重新渲染。
让我们澄清另一个常见的困惑:React 服务端组件不是服务端渲染的替代品。你不应该认为 React 服务端组件是“SSR 2.0”。
相反,我喜欢把它看作是两个可以完美嵌合的拼图片段,两种互补的风味。
我们仍然依赖服务端渲染来生成初始 HTML。React 服务端组件在此基础上,允许我们从客户端 JavaScript 包中省略某些组件,确保它们只在服务器上运行。
事实上,即使不使用服务端渲染,也可以使用 React 服务端组件,尽管在实践中,把两者结合使用可以获得更好的效果。如果你想看例子,React 团队已经建立了一个不使用 SSR 的最小化 RSC 演示。
兼容环境
通常情况下,当推出新的React功能时,我们可以通过将React依赖项升级到最新版本来在现有项目中开始使用它。快速运行npm install react@latest命令,就可以开始使用了。
不幸的是,React服务端组件并不像这样工作。
据我了解,React服务端组件需要与React之外的许多其他内容进行紧密集成,例如打包工具、服务器和路由器。
截至我撰写此文时,开始使用React服务端组件的唯一方法是使用Next.js 13.4+,并使用其全新重新架构的“App Router”。
希望在将来,更多基于React的框架将开始整合React服务端组件。一个核心的React功能只在一个特定的工具中可用,这感觉有些尴尬!React文档中有一个“Bleeding-edge frameworks”(最前沿的框架)部分,列出了支持React服务端组件的框架;我计划不时查看该页面,以了解是否有新的选择可用。
指定客户端组件
在这个新的“React 服务器组件”范例中,默认情况下所有组件都被假定为服务器组件。我们必须“选择加入”客户端组件。
我们通过指定一个全新的指令来做到这一点:
'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服务端组件范例中,默认情况下将组件视为服务器组件。实际上,'use server'用于服务器操作,这是一个完全不同的功能,超出了本博文的范围。
你可能会猜测:我应该如何决定一个组件应该是服务端组件还是客户端组件?
总的来说,如果一个组件可以是服务端组件,那它就应该是服务端组件。服务端组件往往更简单,更容易推理。性能也更好:因为服务端组件不会运行在客户端,它们的代码不会被打包到 JavaScript 中。React 服务端组件范式的好处之一是它有可能改善可交互时间(TTI)指标。
也就是说,我们也不应该把使客户端组件尽可能少作为目标!我们不应该试图优化最小数量的客户端组件。值得记住,在此之前,每一个 React 应用中的每个组件都是客户端组件。
当你开始使用 React 服务端组件时,你会发现这很直观。一些组件需要在客户端运行,因为它们使用 state 或 effects。你可以在这些组件上加一个 'use client' 指令。否则,它们可以留作服务端组件。
边界
当我开始熟悉React服务端组件时,我最初遇到的一个问题是:当props发生变化时会发生什么?
例如,假设我们有一个如下所示的服务器组件:
function HitCounter({ hits }) {
return (
<div>
Number of hits: {hits}
</div>
);
}
假设在初始服务器端渲染中,点击次数等于 0。然后,该组件将生成以下标记:
<div>
Number of hits: 0
</div>
但是,如果 hits 的值改变了呢?假设它是一个 state 变量,从 0 变成了 1。HitCounter 需要重新渲染,但是它不能重新渲染,因为它是一个服务端组件!
关键是,服务端组件单独使用并不合理。 我们需要拉远视角,从更全局的角度考虑我们的应用结构。
假设我们有如下组件树:
如果所有组件都是服务端组件,那么一切都说得通。props 永远不会改变,因为没有组件会重新渲染。
但是假设 Article 组件拥有 hits 状态变量。为了使用状态,我们需要将其转换为客户端组件:
你看到问题了吗?当 Article 重新渲染时,它拥有的组件也会重新渲染,包括 HitCounter 和 Discussion。但是如果它们是服务端组件,它们就不能重新渲染。
为了防止这种不可能的情况,React 团队添加了一个规则:客户端组件只能导入其他客户端组件。这个 'use client' 指令意味着这些 HitCounter 和 Discussion 实例需要变成客户端组件。
我在学习 React 服务端组件时的最大“灵光一闪”时刻之一,是意识到这种新范式完全是关于创建客户端边界。在实践中,会发生这样的情况:
当我们在 Article 组件中添加 'use client' 指令时,我们创建了一个“客户端边界”。这个边界中的所有组件都会隐式地转换为客户端组件。即使像 HitCounter 这样的组件没有 'use client' 指令,在这种特定情况下它们也会在客户端上水化/渲染。*
这意味着我们不需要在每个需要在客户端运行的文件中添加 'use client'。在实践中,只有在创建新的客户端边界时才需要添加它。
解决方法
当我第一次了解到客户端组件不能渲染服务端组件时,我觉得这很具有限制性。如果我需要在应用的较高层使用状态怎么办?这是不是意味着所有东西都需要变成客户端组件??
结果表明,在许多情况下,我们可以通过重构应用程序以改变所有者来绕过这个限制。
这是一件很难解释的事情,所以让我们举个例子:
'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
—— 也会隐式地成为客户端组件。
要解决这个问题,我们可以将颜色管理逻辑提取到它自己的组件和文件中:
// /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
,我们像这样使用这个新组件:
// /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'
指令,因为它不再使用 state 或其他客户端 React 特性。这意味着 Header
和 MainContent
不再会被隐式转换为客户端组件!
但是等一下。 ColorProvider
,一个客户端组件,是 Header
和 MainContent
的父组件。不管怎样,它在树中的位置仍然更高,对吧?
但是就客户端边界而言,父子关系并不重要。导入和渲染 Header
和 MainContent
的是 Homepage
。这意味着 Homepage
决定了这些组件的 props。
记住,我们要解决的问题是服务端组件不能重新渲染,因此不能给它们的任何 props 新的值。通过这个新的设置,决定 Header
和 MainContent
props 的是 Homepage
,而 Homepage
是一个服务端组件,所以没有问题。
这些概念很难理解。 即使有多年 React 经验,我还是觉得非常困惑😅。这需要相当多的练习来培养直觉。
更准确地说,'use client'
指令作用于文件/模块层面。在客户端组件文件中导入的任何模块也必须是客户端组件。当打包器打包我们的代码时,它会根据这些导入进行跟踪!
在我上面的例子中,你可能已经注意到没有方法改变颜色主题。setColorTheme 从未被调用过。
我想尽可能保持最小化,所以省略了一些内容。完整的例子会使用 React 上下文使设置器函数对所有子孙组件可用。只要消费上下文的组件是一个客户端组件,一切都可以良好工作!
窥视底层
让我们从更底层来看。当我们使用服务端组件时,输出是什么样子的?到底生成了什么?服务端组件的输出是一段预渲染的静态 HTML。
让我们从一个超级简单的 React 应用程序开始:
function Homepage() {
return (
<p>
Hello world!
</p>
);
}
在React Server Components范式中,默认情况下,所有组件都是服务器组件。由于我们没有明确将此组件标记为客户端组件(或在客户端边界内渲染它),它将仅在服务器上呈现。
当我们在浏览器中访问此应用程序时,我们将收到类似以下内容的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 服务端组件没有直接关系。
在下面,我们有一个 <script>
标签来加载 JS bundle。这个 bundle 包含依赖如 React,以及应用中使用的任何客户端组件。而由于我们的 Homepage 组件是一个服务端组件,该组件的代码不会被打包进来。
最后,我们有第二个 <script>
标签,其中包含一些内联 JS:
self.__next['$Homepage-1'] = {
type: 'p',
props: null,
children: "Hello world!",
};
这是真正有趣的部分。本质上,我们在这里告诉 React “嘿,我知道你缺少 Homepage 组件代码,但别担心:这里是它渲染的结果”。
通常,当 React 在客户端水化时,它会快速渲染所有组件,构建应用的虚拟表示。它无法对服务端组件这样做,因为代码没有被打包。
因此,我们发送渲染后的值,也就是服务端生成的虚拟表示。当 React 在客户端加载时,它会重用这个描述而不是重新生成。
这就是上面的 ColorProvider 例子能够工作的原因。Header 和 MainContent 的输出通过 children prop 传递给 ColorProvider 组件。ColorProvider 可以随意重新渲染,但这个数据是静态的,被服务端锁定。
如果你想了解服务端组件的序列化和通过网络发送的真实表示,可以查看开发者 Alvar Lagerlöf 的 RSC Devtools。
服务端组件不需要服务端
前文中我提到,服务端渲染是许多不同渲染策略的“总称”,包括:
- 静态:HTML 在构建应用时生成,在部署过程中。
- 动态:HTML 是“按需”生成的,在用户请求页面时。
React 服务端组件与这两种渲染策略兼容。 当我们的服务端组件在 Node.js 运行时中渲染时,它们返回的 JavaScript 对象会被创建。这可以按需发生,也可以在构建时发生。
这意味着可以在没有服务端的情况下使用 React 服务端组件!我们可以生成一堆静态 HTML 文件,并把它们托管在任意地方。事实上,这就是 Next.js App Router 中的默认行为。除非我们确实需要按需发生,否则所有这些工作都提前在构建时完成。
完全不需要 React 吗?
你可能会想:如果我们的应用中不包含任何客户端组件,那我们是否可以完全不需要下载 React?我们可以通过 React 服务端组件构建一个真正的无 JS 静态网站吗?
问题是,React 服务端组件目前仅在 Next.js 框架内可用,而该框架有大量代码需要在客户端运行,以管理路由等功能。
反直觉的是,这实际上往往能带来更好的用户体验;例如,Next 的路由器可以比典型的 <a>
标签更快处理链接点击,因为它不需要加载一个全新的 HTML 文档。
一个结构良好的 Next.js 应用在 JS 下载期间也可以工作,但 JS 加载完成后会更快更好
建议
React 服务端组件是在 React 中运行只在服务端的代码的第一个“官方”方式。但是如我前面提到的,从更广泛的 React 生态来看,这并不是一个真正新的事物;自2016年起,我们就可以在 Next.js 中运行只在服务端的代码了!
最大的不同在于,我们以前没有在组件内运行只在服务端的代码的方式。
最明显的好处是性能。服务端组件不会被打包到我们的 JS 中,这减小了需要下载的 JavaScript 数量,也减少了需要水化的组件数量:
这对我来说可能是最不令人兴奋的一点。老实说,大多数 Next.js 应用在“可交互时间”方面已经够快了。
如果遵循语义化 HTML 的原则,你的应用大部分应该可以在 React 水化之前就可以工作。链接可以点击,表单可以提交,折叠面板可以展开收起(使用 <details>
和 <summary>
)。对于大多数项目来说,React 水化需要几秒钟是可以接受的。
但我发现一件真正很酷的事: 我们不再需要在功能和包体积之间做出妥协!
例如,大多数技术博客都需要某种语法高亮库。在我的博客上,我使用 Prism。代码示例看起来像这样:
function exampleJavaScriptFunction(param) {
return "Hello world!"
}
一个合适的语法高亮库,支持所有流行的编程语言,可能有几兆字节,远远超过 JS 包的体积限制。因此,我们不得不做出妥协,只保留对任务至关重要的语言和特性支持。
但是,如果我们在服务端组件中执行语法高亮,那么库的代码实际上不会被打包到 JS 中。这样,我们就不需要做任何妥协,可以使用所有的特性。
这就是 Bright 的设计理念,一个面向 React 服务端组件的现代语法高亮包。
这就是我对 React 服务端组件感到兴奋的原因。之前由于考虑包体积无法引入的特性,现在可以在服务端免费运行,没有增加包的大小,还能提升用户体验。
这不仅仅是关于性能和用户体验。在使用 RSC 一段时间后,我很欣赏服务端组件的简单优雅。我们不需要再担心依赖数组、过时闭包、记忆化或其他因“变化”而导致的复杂问题。
目前还处于非常早期阶段。React 服务端组件仅在几个月前才从测试版毕业!我真的很期待在未来几年看到社区如何发展,提出像 Bright 这样的创新解决方案,利用这种新范式。这对 React 开发者来说是一个激动人心的时刻!
完整图片