为什么需要服务端组件RSC
- Published on
- Reading time
- 13 min read
- Likes
前言
在React出现之前,传统的Web开发通常使用ASP和PHP构建 多页面应用(MPA)。这种架构通过在服务器端访问数据库,渲染模板,然后将生成的HTML发送给客户端。然而,这种方式存在一些问题:
服务器负载高,响应时间长:每次用户请求新页面时,服务器都必须重新加载数据并完全重新渲染HTML,这增加了服务器的负担,延长了页面的响应时间,影响用户体验。
前后端耦合紧密,维护困难:这种模式下,前界面的更新和维护变得复杂,因为前后端紧密耦合。随着应用的增长,这种架构难以满足灵活的交互需求和动态内容更新。
用户体验不佳:MPA通常在交互和视觉连续性上表现不如单页面应用(SPA),容易导致用户界面体验的断裂,不利于创建流畅的用户体验。
因此,现代Web开发越来越倾向于使用React等前端框架来构建更动态、响应迅速的SPA。这方式不仅减轻了服务器的压力,也提升了应用的可维护性和体验。
传统SSR解决了什么问题,以及带来的问题
服务器端渲染(SSR)关注于初始页面加载,它向客户端发送预渲染的HTML,然后通过下载的JavaScript进行水合(hydration),使页面变为可交互的React应用。传统的SSR解决了一些SEO和初始加载速度的问题,但也引入了新的挑战:
sequenceDiagram participant 用户 as User participant 浏览器 as Browser participant 服务器 as Server participant 数据库 as Database 用户->>浏览器: 输入网址 浏览器->>服务器: HTTP 请求 Note over 服务器: 解析请求 服务器->>数据库: 查询数据 Note over 数据库: 处理查询 数据库->>服务器: 返回数据 Note over 服务器: 渲染 HTML 服务器->>浏览器: 返回完整的 HTML 页面 浏览器->>用户: 显示页面
1. 所有数据必须从服务器获取,页面才能显示任何内容。
使用传统SSR时,服务器需要在开始渲染之前获取所有必要的数据。如果某个数据源响应缓慢,整个页面的渲染都会被阻塞,导致加载时间增加。
想象你在餐厅点了一道需要长时间准备的菜(如慢炖牛肉)。服务员(服务器)必须等菜完全准备好(数据获取完成)才能上菜(渲染页面)。因此,你需要等待更长时间才能开始用餐(页面加载)。
2. 所有JavaScript必须先下载,才能进行水合。
浏览器接收到渲染的HTML后,需要下载所有的JavaScript代码,才能将静态内容变为可交互的React组件。这意味着在JavaScript加载完成之前,用户无法与页面交互。
这就像你收到了一本需要组装的书(服务器渲染的HTML),你必须先拿到所有的拼图块(JavaScript代码),才能开始阅读和互动(进行水合)。
3. 在水合完成之前,无法与页面进行任何交互。
React在进行水合时,会一次性遍历整个组件树,直到所有组件都完成水合才能结束。在此之前,用户无法与页面上的任何组件进行交互。
就像阅读一本互动故事书(组件树),但在读完整本书之前(完成水合),你无法点击任何互动按钮。这导致加载过程中无法进行任何互动,只能等待所有内容加载完毕。
React 18的Suspense如何缓解传统SSR的问题
为了解决上述问题,React 18引入了Suspense,它允许服务器端HTML流式传输,并在客户端进行选择性水合。通过使用<Suspense>
包裹组件,可以让优先级高的组件先渲染,不被较慢的组件阻塞加载。
sequenceDiagram participant User as 用户 participant Server as 服务器 participant React as React participant Comments as 评论组件 User->>Server: 请求页面 Server->>React: 生成初始 HTML React->>Server: 返回非交互内容的 HTML Server->>User: 响应请求,发送 HTML User->>React: 接收并渲染 HTML React->>React: 初始化非交互内容 React->>Comments: 请求加载 Comments 组件代码 Comments->>Server: 加载 Comments 组件代码 Server->>Comments: 返回 Comments 组件代码 Comments->>React: 提供 Comments 组件 React->>React: 在 Suspense 中水合 Comments 组件 React->>User: 允许与页面交互
当在<Suspense>
中包含多个组件时,React会按照组件在树中的顺序进行渲染,实现流式处理。如果用户尝试与某个组件交互,该组件的加载和水合将优先进行。
<Suspense>
的优点
使用1. 服务器端HTML可以流式传输
通过将页面的某些部分(如主要内容区域)包裹在<Suspense>
中,React无需等待所有数据获取完毕就可以开始为页面的其他部分流式传输HTML。React会先发送占位符(如加载状态),直到主要内容的数据准备好。
2. 客户端可以进行选择性水合
虽然HTML可以流式传输,但JavaScript可能还未完全下载,导致用户暂时无法交互。通过选择性水合,可以优先水合用户首先需要交互的关键部分,而无需等待所有JavaScript文件加载完毕。
举例说明
假设我们有一个电商网站首页,包括以下部分:
- 顶部导航栏
- 产品列表
- 底部推荐栏
在传统的水合过程中,页面会在所有JavaScript加载完成后才开始水合。使用选择性水合,可以优先水合导航栏和产品列表,底部推荐栏稍后再水合。
import React, { Suspense, lazy } from 'react'
const NavBar = lazy(() => import('./NavBar'))
const ProductList = lazy(() => import('./ProductList'))
const Recommendations = lazy(() => import('./Recommendations'))
function HomePage() {
return (
<div>
<Suspense fallback={<div>Loading NavBar...</div>}>
<NavBar />
</Suspense>
<Suspense fallback={<div>Loading Product List...</div>}>
<ProductList />
</Suspense>
<Suspense fallback={<div>Loading Recommendations...</div>}>
<Recommendations />
</Suspense>
</div>
)
}
export default HomePage
在这个示例中,我们使用了React的Suspense
和lazy
实现组件的懒加载。页面会优先加载和渲染NavBar
和ProductList
,Recommendations
部分会稍后加载和水合。
工作原理
- 用户访问首页时,服务器返回包含初始HTML的页面。
- 客户端开始下载主要JavaScript包。
NavBar
和ProductList
的JavaScript代码优先下载并解析,页面开始对这些部分进行水合。Recommendations
部分稍后下载并解析,在主要部分完成后进行水合。
这种方式加快了页面关键部分的水合速度,提高了用户体验,因为用户可以更早地开始与页面交互。
使用Suspense的局限性
虽然Suspense
提高了用户体验,但在纯SSR中仍存在一些局限:
下载过多的代码:尽管JavaScript代码可以异步加载,但用户最终仍需下载整个应用的代码。随着应用功能的增加,代码量也会增大,导致用户下载大量可能不必要的代码。
无意义的客户端处理:所有的React组件,无论是否需要交互,都需要在浏览器中进行水合。这增加了客户端的负担,延长了页面可交互的时间。
设备性能负担:大部分JavaScript代码的执行都发生在用户的设备上,对于性能较弱的设备,可能导致网页变慢或卡顿。
React需要服务端组件(RSC)
基于以上问题,React引入了服务端组件(RSC),它进一步区分了服务端组件和客户端组件,细化了组件的颗粒度。RSC可以单独获取数据,并完全在服务器上渲染,生成的HTML会流入客户端的React组件树,根据需要与其他服务端和客户端组件交叉使用。
这一过程消除了客户端重新渲染的需要,提高了性能。对于任何客户端组件水合可以与RSC流入同时发生,因为计算负载在客户端和服务器之间共享。
换句话说,服务器更强大,且更接近数据源,它处理计算密集的渲染,只向客户端发送必要的交互代码片段。当RSC因状态更改需要重新渲染时,它会在服务器上刷新,并无缝合并到现有的DOM中,客户端状态得以保留。
服务端组件(RSC)的优势
1. 提升性能,降低Bundle大小
在传统的Web应用中,浏览器需要下载并执行整个页面的所有代码和数据依赖,可能包括用户当前页面不需要的部分。而使用RSC,服务器处理所有的依赖关系,只将结果和必要的客户端组件发送到浏览器。这意味着页面加载速度更快,客户端需要下载的JavaScript代码更少,且大小是固定的,不会随着应用的增长而增大。
2. 与Suspense的无缝集成
RSC能够与客户端代码无缝结合,可以在同一个React组件树中同时使用客户端和服务端组件。通过将大部分代码移到服务器端处理,RSC避免了客户端常见的数据请求“瀑布流”问题。服务器端可以更快速地获取并处理数据,减少等待时间。
在RSC中,数据获取和渲染都在服务器上完成,Suspense
也可以在服务器端管理等待,减少了来回的延迟,使页面更快地渲染并展示给用户。
参考链接
感谢以下资源的帮助和启发:
本文采用CC BY-NC-SA 4.0 - 非商业性使用 - 相同方式共享 4.0 国际进行许可。