SSR 指的是服务端渲染,直出 html 到客户端,减少首屏白屏时间,这里不对 SSR 过多说明,有兴趣自行查找。
hydration
react 服务端渲染需要经历以下的步骤
- 服务器请求数据
- 将数据渲染成 html,输出到客户端
- 客户端加载 js 代码
- 将 js 逻辑与服务器生成的 html 绑定
渲染组件和事件绑定的整个处理过程叫做 hydration。但是整个过程是串行的,必须要等上一步完成才能执行下一步操作。如果项目规模大,某一部分组件生成比较慢,会导致整个网站渲染慢,那体验就很差
React18 会使用 Suspense 来将你的应用程序分解成较小的独立单元。这些单元将独立完成这些步骤,并且不会阻碍应用程序的其他部分。因此,你的应用程序的用户将更快地看到内容,并能更快地开始与应用程序交互。应用程序中最慢的部分不会拖累那些较快的部分。这些优化在 react18 内部是自动完成的
Suspense
既然 SSR 整个过程是依赖强相关的,react18 需要解决两个问题
- 流式渲染,要使用此功能,必须要从原先的
renderToString
切换到pipeToNodeWritable
- 选择性 hydration,在客户端使用
createRoot
方法,用<Suspense>
包装比较慢的组件(相对不那么重要的组件)
<Layout> <NavBar /> <Sidebar /> <RightPane> <Post /> <Suspense fallback={<Spinner />}> <Comments /> </Suspense> </RightPane></Layout>
使用 <Suspense>
包装 <Comments />
组件,react 在服务端渲染的时候,不需要等待 comments 组件完成渲染再返回给客户端,可以先展示加载,上面的代码最终会渲染成
<main> <nav> <!--NavBar --> <a href="/">Home</a> </nav> <aside> <!-- Sidebar --> <a href="/profile">Profile</a> </aside> <article> <!-- Post --> <p>Hello world</p> </article> <section id="comments-spinner"> <!-- Spinner --> <img width="400" src="spinner.gif" alt="Loading..." /> </section></main>
接下来服务器会继续准备 comment 组件的 html,如果生成结束,就会将额外的 html 发送到同一个流中,并代入一个可执行的 script
<div hidden id="comments"> <!-- Comments --> <p>First comment</p> <p>Second comment</p></div><script> // This implementation is slightly simplified document .getElementById('sections-spinner') .replaceChildren(document.getElementById('comments'));</script>
React.lazy
虽然可以提前发送 html,但是首次渲染输出的 js 过大,也需要一些执行时间,通常在客户端渲染无非做两件事,代码拆分,按需加载。
React.lazy 可以在服务端完成这些事了,我们把评论组件的代码从主包中分割出来。
import { lazy } from 'react';const Comments = lazy(() => import('./Comments.js'));// ...<Suspense fallback={<Spinner />}> <Comments /></Suspense>;
这样 react 在除了 comment 组件之外的其他模块,都可以提前 hydration,不需要等待所有代码加载完成再进行 hydration
当多个 Sunspend 一起使用的时候,会出现一个问题,页面加载后优先 hydration 哪部份组件呢?
<Layout> <NavBar /> <Suspense fallback={<Spinner />}> <Sidebar /> </Suspense> <RightPane> <Post /> <Suspense fallback={<Spinner />}> <Comments /> </Suspense> </RightPane></Layout>
现在有 SideBar 和 Comments 组件都被 Suspense 包裹,react 默认会从较早发现的 Suspense 组件开始进行 hydration ,这个例子,是侧边栏
但是用户此时不对侧边进行交互,而优先对评论区域进行交互
React 会记录点击,优先给评论区域进行 hydration ,当完成后,React“重放”记录的点击事件(通过再次派发),并让你的组件对互动做出反应。 接下来再继续给侧边栏进行 hydration
总结
React18 为 SSR 提供了两个主要功能
流式渲染,尽早发送 html,通过 Suspense 实现,Suspense 组件生成的 html 和 script 将会延迟发送给客户端。并在正确位置执行
选择性 hydration 让你在 HTML 和 JavaScript 代码完全下载之前,尽早开始为你的应用程序进行 hydration。它还优先为用户正在互动的部分进行 hydration,创造一种即时 hydration 的错觉