对我们的下一本书感兴趣吗?了解更多关于使用 React 构建大型 JavaScript Web 应用程序

渲染模式

选择性水合

之前的文章中,我们介绍了 SSR 与水合如何改善用户体验。React 能够(快速)使用 react-dom/server 库提供的 renderToString 方法在服务器上生成树,该树在整个树生成后被发送到客户端。渲染的 HTML 是非交互式的,直到 JavaScript 包被获取和加载后,React 才会向下遍历树以进行水合并附加处理程序。

但是,这种方法会导致一些性能问题,因为当前实现存在一些限制。

在服务器渲染的 HTML 树能够被发送到客户端之前,所有组件都需要准备好。这意味着可能依赖于外部 API 调用或可能导致一些延迟的任何过程的组件,最终可能会阻止较小的组件快速渲染。

除了树生成速度较慢之外,另一个问题是 React 只能对树进行一次水合。这意味着在 React 能够对任何组件进行水合之前,它需要先获取所有组件的 JavaScript,然后才能对其中任何一个进行水合。这意味着较小的组件(具有较小的包)必须等到较大的组件的代码被获取和加载,直到 React 能够对网站上的任何内容进行水合。在此期间,网站保持非交互状态。

React 18 通过允许我们结合流式服务器端渲染和一种新的水合方法来解决这些问题:选择性水合!


我们可以不再使用之前介绍的 renderToString 方法,而是可以使用服务器上新的 pipeToNodeStream 方法流式渲染 HTML。

这种方法结合 createRoot 方法和 Suspense,使得在无需等待较大的组件准备好即可开始流式传输 HTML 成为可能。这意味着我们可以在使用 SSR 时延迟加载组件,这在之前是(真正)不可能的!

server.js
App.js
index.js
1import { pipeToNodeStream} from "react-dom/server";
2
3export function render(res) {
4 const data = createServerData();
5 const { startWriting, abort } = pipeToNodeWritable(
6 <DataProvider data={data}>
7 <App assets={assets} />
8 </DataProvider>,
9 res,
10 {
11 onReadyToStream() {
12 res.setHeader('Content-type', 'text/html');
13 res.write('<!DOCTYPE html>');
14 startWriting();
15 }
16 }
17 );
18};

这是一个受此 codesandbox启发的简化示例

Comments 组件(以前会减慢树生成和 TTI)现在被包装在 Suspense 中。这告诉 React 不要让此组件减慢其余树的生成速度。相反,React 将回退组件作为最初渲染的 HTML 插入,并在将其发送到客户端之前继续生成其余的树。

同时,我们仍在获取 Comments 组件所需的外部分数据。

选择性水合使得即使在 Comments 组件被发送之前,也能对已经发送到客户端的组件进行水合!

一旦 Comments 组件的数据准备就绪,React 就会开始流式传输此组件的 HTML,以及一个小的 <script> 来替换回退加载程序。

在注入新的 HTML 后,React 开始水合。


React 18 修复了人们在使用 React 与 SSR 时经常遇到的问题。

流式渲染允许你一旦组件准备就绪就开始流式传输,而不会因为在服务器上生成可能需要更长时间的组件而导致 FCP 和 TTI 降低。

一旦组件流式传输到客户端,就可以对其进行水合,因为我们不再需要等待所有 JavaScript 加载才能开始水合,并且可以在所有组件都完成水合之前开始与应用程序交互。


参考文献