从业务的角度来看 React18 Suspense SSR 架构

image

目录

1. 实际业务的困境

现有的服务端渲染(Server-side rendering,简称 SSR)的原理是当 HTML 请求到达 Node 端时先等待后端接口数据请求完成(30~300ms),然后再进行渲染(2~5ms),最后再响应渲染完成的页面给浏览器。

大致流程是: fetch data (server) → render to HTML (server) → load code (client) → hydrate (client)

如本文用作示例的商品管理页面,需要并发8个后端接口请求,最慢的接口 /api/xxx/goodsList 延时为 246.6 ms,导致Step1阶段用户看到的页面白屏时间至少是 246.6ms + 5ms

image

💡 Step2 截图为灰色仅为了区别于 Step3 可交互状态,实际用户看到的效果与 Step3 无差异

为了解决后端接口延时不可控造成的 Step1 阶段白屏时间过长的问题,于是我们开发了渐进式渲染功能,优化后的渲染链路变成了如下

image

2. Suspense SSR 架构

React18 新的 Suspense SSR 架构允许你在服务端使用 Suspense 组件,比如你的 Comments 组件是需要后端接口的数据,那么可以做到后端接口数据仅阻塞 Comments 组件,不会阻塞整个 App 组件的渲染与提前返回

1
2
3
4
5
6
7
8
9
10
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>

新 Suspense SSR 架构下的渲染链路变成了如下
image

2.1. 可能存在的问题

你可能想到部分可交互状态时,如果客户端其他组件响应了事件导致 Comment 组件的 props 变化,而服务端是根据 initProps 对 Comment 进行的渲染,那么 React 会如何取舍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function Content() {
const [count, setCount] = useState(0);

return (
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<h2
onClick={() => {
setCount(count + 1)
console.log("setCount 点击事件测试, count: ", count);
}}
>
setCount 点击事件测试
</h2>
<Suspense fallback={<Spinner />}>
<Comments count={count}/>
</Suspense>
</RightPane>
</Layout>
);
}

function Comments({ count }) {
const comments = useData();

return (
<>
<span>count: {count}</span>
{comments.map((comment, i) => (
<p className="comment" key={i}>
{comment}
</p>
))}
</>
);
}

从测试结果来看 Props 发生变化后 React 会以客户端最新渲染的结果为准, 与此同时抛出Uncaught Error: This Suspense boundary received an update before it finished hydrating.错误
image

3. 应用到业务中的效果

因为 Suspense 支持对于单个组件进行的延迟渲染,首先我们需要对页面组件进行拆分,同时使用 Suspense 进行包裹

image

如果升级到了新 Suspense SSR 架构下的渲染链路变成了如下
image

4. 小结

Suspense SSR 架构解决了服务端渲染各个流程串行等待问题,强调一切按需(懒加载,懒编译,现在是懒渲染?)进行
  • 渐进式渲染像是 React 原生不支持 Suspense SSR 下的模拟实现
渐进式渲染首屏比 Suspense SSR 更加完整
  • 渐进式渲染: 服务端渲染时虽然没有接口数据,但根据 initState 能够渲染出较完整的首屏
  • Suspense SSR: 需要接口数据的组件首屏都是渲染的占位组件,如 Spinner
Suspense SSR 类似于懒渲染,设计理念更加符合现代化 Web 开发

5. 最后的话

如果发现升级后页面没有进行分块渲染, 或许你要继续阅读 👉 服务端流式渲染 iOS 中踩坑记