SSR指的是服务端渲染,直出html到客户端,减少首屏白屏时间,这里不对SSR过多说明,有兴趣自行查找。

hydration

react服务端渲染需要经历以下的步骤

  1. 服务器请求数据

  2. 将数据渲染成html,输出到客户端

  3. 客户端加载js代码

  4. 将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的错觉

参考资料

New Suspense SSR Architecture in React 18