前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 服务器组件:引领下一代 Web 开发潮流

React 服务器组件:引领下一代 Web 开发潮流

作者头像
童欧巴
发布2024-04-10 09:57:26
1360
发布2024-04-10 09:57:26
举报
文章被收录于专栏:前端食堂前端食堂

“原文:https://www.builder.io/blog/why-react-server-components 翻译及润色:童欧巴

大家好,我是童欧巴。

在过去十年中,React 及其生态系统经历了不断的发展。每一个版本都带来了新概念、优化乃至范式的转变,不断推动着我们对于网页开发可能性的认识边界。

React 服务器组件(RSC)是至今为止继 React Hooks 之后最重要的变革。然而,这一变化在社区中引起了不同的反响。

对我而言,Linkin Park 的这句歌词很好地表达了我们在迈向 2024 年时对 React 进化的感受:

“因为一旦人们对某样事物的工作原理形成了理论,大家都希望下一个新鲜事物能和第一个一模一样。 ”

我们对我们熟知并喜爱的 React 已经习以为常,以至于面对一次范式转变,不可避免地带来了犹豫和怀疑。

这篇博文的目的是,带你了解 React 多年来在渲染技术上的演变历程,帮助你理解为什么 React 服务器组件不仅是必然的,也是构建高性能、低成本、提供卓越用户体验的 React 应用未来的关键。

客户端渲染(CSR)

如果你已经在开发领域有一段时间了,你会记得 React 曾是构建单页应用(SPA)的首选库。

在典型的 SPA 中,当客户端发出请求时,服务器会发送一个单一的 HTML 页面给浏览器(客户端)。这个 HTML 页面通常只包含一个简单的 div 标签和一个 JavaScript 文件的引用。这个 JavaScript 文件包含了运行你的应用所需的一切,包括 React 库本身及你的应用代码,它会在 HTML 文件解析时下载。

下载的 JavaScript 代码会在你的计算机上生成 HTML,并将其插入到根 div 元素下的 DOM 中,于是你就能在浏览器中看到用户界面。

当你在 DOM 检查器中看到 HTML 出现,但在“查看源代码”选项中看不到时,就能明显看出这一过程。这个选项显示的是服务器发送到浏览器的 HTML 文件。

这种直接在浏览器(客户端)中将组件代码转换为用户界面的渲染方法,称为客户端渲染(CSR)。

以下是对客户端渲染的直观展示:

接下来是对 React 单页面应用(SPA)的 DOM 检查器和页面源代码的比较:

客户端渲染(CSR)迅速成为了 SPA 的标准,并被广泛采纳。然而,不久开发者们就开始意识到这种方法存在一些固有的缺陷。

CSR 的不足之处

首先,仅生成包含单一 div 标签的 HTML 对于搜索引擎优化(SEO)并不是最佳选择,因为它几乎不提供搜索引擎可索引的内容。大型的包文件以及深层嵌套组件引起的 API 响应请求瀑布,可能导致有价值的内容无法足够快地被渲染,并由爬虫程序索引。

其次,全部让浏览器(客户端)来负责,如数据获取、UI 计算及使 HTML 变得可交互的任务,会使过程变慢。用户可能会看到一个空白屏幕或者加载动画,等待页面加载。随着应用不断增加新特性,JavaScript 包的体积也随之增大,使得用户等待可见 UI 的时间变长,这种延迟对于网速较慢的用户来说尤其明显。

虽然 CSR 为我们现在所熟悉的互动式网络应用铺平了道路,但为了提高 SEO 和性能,开发者开始寻找更好的解决方案。

服务器端渲染(SSR)

为了解决 CSR 的不足,现代 React 框架,如 Next.js,转向了服务器端的解决方案,这种方法从根本上改变了内容是如何被传递给用户的。

与发送一个几乎为空并依赖客户端 JavaScript 构建页面的 HTML 文件不同,服务器负责渲染完整的 HTML。然后,这个完整生成的 HTML 文档直接被发送到浏览器。由于 HTML 在服务器上生成,浏览器能够迅速地解析和展示它,从而改善了初始页面的加载时间。

以下是对服务器端渲染的直观展示:

解决 CSR 缺点

服务器端方法有效地解决了 CSR 带来的问题。

首先,它大幅改进了 SEO,因为搜索引擎能够轻易地对服务器渲染的内容进行索引。

其次,浏览器可以立即显示页面的 HTML 内容,而非仅展示一个空白屏幕或加载图标。

Hydration(注水/水合技术)

SSR 对立即提升内容可见性的方法带来了自己的复杂性,特别是关于页面互动性的部分。完整页面的互动性需要等到 JavaScript 包(包括 React 自身及应用特定代码)完全下载并由浏览器执行后才能实现。

这一重要阶段称为“hydration”,即服务器最初提供的静态页面被激活。在 hydration 过程中,React 在浏览器中接管,根据服务端提供的静态 HTML 重建内存中的组件树,并精心安排树内的互动元素位置。

然后,React 开始将必要的 JavaScript 逻辑绑定至这些元素,包括初始化应用状态、为点击和鼠标悬停等行为附加事件处理器,以及设置其他必要的动态功能,为用户提供完全互动的体验。

SSG 与 SSR

深入来看,服务器端解决方案可分为两个策略:静态站点生成(SSG)和服务器渲染(SSR)。

SSG 在构建时发生,即应用部署到服务器上时。生成的页面已经渲染好,随时可以提供服务。这适合内容变化不频繁的场景,如博客文章。

另一方面,SSR 根据用户请求动态渲染页面。它适合个性化内容,如社交媒体动态,HTML 内容依赖于登录用户。通常,这两种方法被统称为服务器端渲染,或简称 SSR。

服务器端渲染(SSR)相对于客户端渲染(CSR)是一个重大的进步,提供了更快的初始页面加载速度和更佳的 SEO。然而,SSR 也带来了一系列的挑战。

SSR 的挑战

SSR 的一个挑战是,组件无法在开始渲染后再“等待”数据加载。如果一个组件需要从数据库或其他源(如 API)获取数据,这个获取过程必须在服务器开始渲染页面之前完成。这可能会延迟服务器响应浏览器的时间,因为服务器必须收集所有必要数据后,才能将页面的任何部分发送给客户端。

SSR 的第二个挑战是,为了成功实现 hydration,即 React 使服务器渲染的 HTML 变得可互动,浏览器中的组件树必须与服务器生成的组件树完全一致。这意味着,所有组件的 JavaScript 必须在客户端加载完毕后,才能开始任何一个组件的 hydration。

SSR 的第三个挑战关于 hydration 本身。React 一次性完成组件树的 hydration,意味着一旦开始,就不会停止直到整个树完成。因此,在所有组件完成 hydration 前,无法与任何一个组件互动。

这三个挑战:必须加载整个页面的数据、整个页面的 JavaScript,以及对整个页面进行 hydration,构成了一个从服务器到客户端的“全有或全无”的瀑布效应问题,每个问题必须在转向下一个之前解决。如果应用的某些部分比其他部分慢,这会非常低效,这在现实世界的应用中是常有的情况。

因这些限制,React 团队引入了一个新的、改进的 SSR 架构。

服务器渲染的 Suspense

React 18 引入了用于 SSR 的 Suspense,旨在解决传统 SSR 的性能缺点。这种新架构让你可以使用 <Suspense> 组件来启用两大 SSR 特性:

  • 服务器上的 HTML 流式传输
  • 客户端上的选择性 hydration

服务器上的 HTML 流式传输

正如之前讨论的,传统 SSR 是一种“全有或全无”的做法。服务器渲染完整 HTML 后发送至客户端。客户端展示此 HTML,且仅在整个 JavaScript 包加载完毕后,React 才开始为整个应用进行 hydration 以增加互动性。

以下是上述过程的直观展示:

然而,随着 React 18 的到来,我们迎来了新的可能性。通过使用 React Suspense 组件包裹页面的某个部分,比如主内容区,我们告诉 React 在开始为页面其余部分流式传输 HTML 之前,不必等待主部分的数据全部获取完毕。React 将会先发送一个占位符,例如一个加载图标,而非完整内容。

当服务器准备好主内容区的数据时,React 会通过持续的流发送额外的 HTML,并通过一个内联 <script> 标签附带必要的最小量 JavaScript,以确保该 HTML 能被正确展示。因此,即便客户端尚未加载完整的 React 库,用户也能先看到主内容区域的 HTML。

以下是使用 <Suspense> 实现 HTML 流式传输的直观展示:

这解决了我们的第一个问题:在展示页面任何内容之前,不必先下载所有数据。如果某个特定区域的数据加载导致了初始 HTML 的延迟,该区域可以后续无缝地整合进流中。这正是 <Suspense> 支持服务器端 HTML 流式传输的关键所在。

客户端的选择性 hydration

尽管我们现在能够加快初始 HTML 的传送速度,但还有一个挑战未解。在主内容区的 JavaScript 加载完成之前,客户端应用的 hydration 过程无法开始。如果主内容区的 JavaScript 包很大,这可能会显著延缓整个过程。

为了缓解这个问题,可以采用代码分割技术。代码分割意味着你可以标记特定代码段作为非立即加载项,让你的打包工具将它们分割到不同的 <script> 标签中。

利用 React.lazy 进行代码分割可以把主内容区的代码与主 JavaScript 包分离。因此,包含 React 和整个应用的代码(除主要部分外)现在可以被客户端独立下载,而无需等待主内容区的代码加载。

这一点至关重要,因为通过将主内容区包裹在 <Suspense> 中,你已经向 React 表示,它不应该阻止页面的其他部分进行流式传输乃至 hydration。这个称为选择性 hydration 的功能,允许在其余 HTML 和 JavaScript 代码完全下载之前,就对可用的部分进行 hydration。

从用户的角度看,他们最初接收到的是以 HTML 形式流入的非交互内容。然后你指示 React 开始 hydration 过程。尽管主内容区的 JavaScript 代码还未就绪,但没关系,因为我们可以选择性地对其他组件进行 hydration。

主内容区的代码加载完成后,就会进行 hydration。

得益于选择性 hydration,即使是体积庞大的 JS 代码也不会阻止页面其他部分变得可交互。

以下是利用 <Suspense> 实现选择性 hydration 的直观展示:

此外,选择性 hydration 解决了第三个问题:“要与任何东西互动,必须对所有东西进行 hydration”。React 会尽可能早地开始 hydration 过程,这样用户就能在等待主内容区完成 hydration 之前与诸如页眉和侧导航这样的元素互动。这一过程由 React 自动管理。

在多个组件等待 hydration 的情况下,React 会根据用户的交互行为来优先处理 hydration。举个例子,如果侧边栏正准备进行 hydration,而你点击了主内容区,React 将在点击事件的捕获阶段立即对被点击的组件进行同步 hydration。这确保了组件能够立即对用户的交互做出反应。侧导航的 hydration 则会稍后进行。

以下是基于用户交互进行 hydration 的直观展示:

SSR 的 Suspense 缺陷

首先,尽管 JavaScript 代码是以异步方式流式传输到浏览器的,但用户最终还是需要下载网页的全部代码。随着应用增加更多功能,用户需要下载的代码量也随之增加。这就引出了一个重要的问题:用户真的需要下载如此多的数据吗?

其次,目前的做法要求所有 React 组件在客户端进行 hydration,不考虑它们实际上是否需要交互性。这一过程可能会浪费资源,并延长用户的加载时间及互动时间,因为设备需要处理和渲染那些可能根本不需要客户端交互的组件。这引出了另一个问题:是否所有组件都需要进行 hydration,即使是那些不需要交互性的组件?

第三,尽管服务器具备处理密集型处理任务的优越能力,但大量的 JavaScript 执行仍然在用户的设备上进行。这可能会降低性能,特别是在性能不强的设备上。这又引出了一个重要问题:是否真的需要在用户设备上完成如此多的工作?

为了应对这些挑战,仅采取渐进式的步骤是不够的。我们需要向一个更加强大的解决方案迈出重要的一步。

React 服务器端组件(RSC)

React 服务器端组件(RSC)是由 React 团队设计的一种新架构。这种方法旨在充分利用服务器和客户端环境的优势,优化效率、加载时间和互动性。

该架构引入了一种双组件模型,区分了客户端组件和服务器端组件。这种区分不是基于组件的功能,而是基于它们的执行位置和它们被设计来与之交互的特定环境。我们来仔细看看这两种类型:

客户端组件

客户端组件是我们在之前的渲染技术中已经使用并讨论过的熟悉的 React 组件。它们通常在客户端(CSR)进行渲染,但也可以在服务器上(SSR)渲染一次,使用户能够立即看到页面的 HTML 内容,而不是一个空白屏幕。

将“客户端组件”在服务器上渲染可能听起来有些让人困惑,但把它们看作主要在客户端运行的组件是有益的,这些组件可以(也应该)作为一种优化策略在服务器上执行一次。

客户端组件可以访问客户端环境,如浏览器,这允许它们使用状态、效果和事件监听器来处理交互性,并访问像地理位置或 localStorage 这样的浏览器专属 API,让你为特定用例构建前端,正如我们在 RSC 架构引入之前多年来一直做的那样。

实际上,“客户端组件”这个术语并不指代任何新事物;它仅仅帮助我们将这些组件与新引入的服务器端组件区分开来。

这里有一个计数器客户端组件的例子:

代码语言:javascript
复制
"use client"

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>Counter</h2>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

服务器组件

服务器组件是一种特别设计的 React 组件,专为仅在服务器上运行而生。与客户端组件不同,它们的代码保留在服务器上,永远不会被下载到客户端。这种设计决策为 React 应用带来了多重益处,下面我们来详细探讨这些益处。

零包体积

首先,从包体积角度看,服务器组件不会将代码发送到客户端,允许大型依赖项保留在服务器。这对于互联网连接速度慢或设备性能较低的用户特别有利,因为它免除了他们下载、解析及执行这些组件的 JavaScript 的需要。此外,它还省去了 hydration 步骤,加速应用的加载和交互速度。

直接访问服务器资源

其次,服务器组件能够直接访问后端的服务器资源,如数据库或文件系统,从而无需额外的客户端处理即可实现高效的数据抓取和渲染。利用服务器的计算能力和靠近数据源的优势,它们能够处理计算密集的渲染任务,并仅向客户端发送交互部分的代码。

增强安全

第三,服务器端组件的独有服务器端执行通过将敏感数据和逻辑保留在客户端之外,如令牌和 API 密钥,增强了安全性。

数据获取增强

第四,服务器端组件提高了数据抓取的效率。传统上,在客户端使用 useEffect 进行数据抓取时,子组件不能开始加载其数据,直到父组件已经完成了自己的加载。这种顺序数据抓取常常导致性能低下。

主要问题不在于往返本身,而在于这些往返是从客户端向服务器进行的。服务器端组件允许应用将这些顺序往返转移到服务器端,通过将逻辑移到服务器,减少了请求延时并改善总体性能,避免客户端与服务器之间的瀑布式请求。

缓存

第五,服务器渲染使得可以缓存结果,这些缓存的结果可以在后续请求中重用,甚至跨不同用户重用。这种方式通过减少每次请求所需的渲染和数据抓取量,显著提升性能并降低成本。

更快的初始页面加载和首次内容呈现

第六,服务器端组件显著提升了初始页面加载和首次内容呈现(FCP)。通过在服务器上生成 HTML,页面能够立即渲染,无需等待下载、解析和执行 JavaScript 的延迟。

改善 SEO

第七,在搜索引擎优化(SEO)方面,服务器渲染的 HTML 对搜索引擎爬虫完全可访问,提高了页面的索引性。

高效的流式传输

最后是流式传输,服务器组件允许将渲染过程分解成可管理的块,这些块一旦准备好就会被流式传输至客户端。这种方式让用户可以更早看到页面的部分内容,无需等待服务器端整个页面全部渲染完成。

这里是一个产品列表页的服务器组件的例子:

代码语言:javascript
复制
export default async function ProductList() {
  const res = await fetch("https://api.example.com/products");
  const products = res.json();

  return (
    <main>
      <h1>Products</h1>
      {products.length > 0 ? (
        <ul>
          {products.map((product) => (
            <li key={product.id}>
              {product.name} - ${product.price}
            </li>
          ))}
        </ul>
      ) : (
        <p>No products found.</p>
      )}
    </main>
  );
}

“use client” 指令

在 React 服务器组件的范式中,有一点非常重要:默认情况下,Next.js 应用中的每一个组件都被视为服务器端组件。

为了定义客户端组件,我们必须在文件的顶部包含一个指令,换句话说,一条特别的指示:“use client”。这个指令就像是我们从服务器端过渡到客户端的通行证,也是我们定义客户端组件的方式。

它向打包工具发出信号,表明该组件及其导入的任何组件都是预期在客户端执行的。因此,该组件获得了完全访问浏览器 API 的能力,并能够处理交互性。

“use server” 指令标记了可以从客户端代码调用的服务器端函数。我们将在另一篇文章中讨论“use server”和“server actions”。

React 服务器组件的渲染生命周期

让我们假设使用 Next.js 作为 React 框架,来探索 RSC 的渲染生命周期。

Vercel 搭配 Next.js 13 是首批支持 React 服务器端组件(RSC)架构的。

对于 React 服务器端组件(RSC),有三个重要元素需要考虑:你的浏览器(客户端)和服务器端的 Next.js(框架)以及 React(库)。

首次加载过程

  • 当你的浏览器发起页面请求时,Next.js 应用的路由将请求的 URL 匹配到一个服务器组件。接着,Next.js 指令 React 渲染该服务器端组件。
  • React 渲染服务器组件及其所有服务器组件的子组件,把它们转换成一种称作 RSC 负载的特殊 JSON 格式。如果有服务器组件发生了挂起(suspend),React 会暂停渲染那一子树,并发送一个占位符。
  • 同时,客户端组已准备好后续生命周期中的指令。
  • Next.js 利用 RSC 负载和客户端组件的 JavaScript 指令在服务器上生成 HTML。这份 HTML 被流式传输到你的浏览器,立即显示路由的快速非交互式预览。
  • 同时,Next.js 在 React 渲染每个 UI 单元时,流式传输 RSC 负载。
  • 在浏览器端,Next.js 处理流式传输的 React 响应。React 利用 RSC 负载和客户端组件指令逐步渲染 UI。
  • 当所有客户端组件及服务器组件的输出都加载完毕后,用户便能看到最终的 UI 状态。
  • 客户端组件经过 hydration 过程,使得我们的应用从一个静态展示转化为一个互动体验。

这是首次加载过程。接下来,我们来看看更新应用部分时的更新过程。

更新过程

  • 浏览器请求刷新特定 UI 部分,如完整路由。
  • Next.js 处理这一请求,并将其与所请求的服务器端组件匹配。然后,Next.js 指示 React 渲染整个组件树,这与首次加载过程类似。
  • 但不同于首次加载的是,更新过程不会生成 HTML。Next.js 会将响应数据逐步流式传输回客户端。
  • 收到流式响应后,Next.js 触发路由使用新的输出进行重渲染。
  • React 会将新渲染的输出与屏幕上现有的组件合并(调和)。因为 UI 描述采用的是特殊的 JSON 格式而非 HTML,React 能够在保持关键 UI 状态(如焦点或输入值)不变的情况下更新 DOM。
  • 这就是 Next.js 中 App Router 进行 RSC 渲染生命周期的精髓所在。

在 React 服务器组件架构中,服务器组件承担数据获取和静态渲染的责任,而客户端组件则负责渲染应用的交互式元素。

总的来说,RSC 架构使得 React 应用能够同时利用服务器和客户端渲染的最佳特性,而且全程使用单一语言、单一框架以及一套统一的 API。RSC 在改进传统渲染技术的同时,也解决了其局限性。

想要了解更多背景信息和对 RSCs 有更全面的理解,请参阅 Next.js 文档[1]或观看我在 YouTube 上的 Next.js 教程[2]。

参考资料

[1]

Next.js 文档: https://nextjs.org/docs/app/building-your-application/rendering

[2]

Next.js 教程: https://www.youtube.com/watch?v=ZjAqacIC_3c&list=PLC3y8-rFHvwjOKd6gdf4QtV1uYNiQnruI

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-01,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 前端食堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 客户端渲染(CSR)
    • CSR 的不足之处
    • 服务器端渲染(SSR)
      • 解决 CSR 缺点
        • Hydration(注水/水合技术)
          • SSG 与 SSR
            • SSR 的挑战
              • 服务器渲染的 Suspense
                • 服务器上的 HTML 流式传输
                • 客户端的选择性 hydration
              • SSR 的 Suspense 缺陷
                • React 服务器端组件(RSC)
                  • 客户端组件
                    • 服务器组件
                      • 零包体积
                      • 直接访问服务器资源
                      • 增强安全
                      • 数据获取增强
                      • 缓存
                      • 更快的初始页面加载和首次内容呈现
                      • 改善 SEO
                      • 高效的流式传输
                    • “use client” 指令
                      • React 服务器组件的渲染生命周期
                        • 首次加载过程
                        • 更新过程
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                    http://www.vxiaotou.com