React-DOM 的 SSR 新架构 Fizz

date
Apr 12, 2021
slug
react-ssr-fizz
status
Published
tags
summary
Basic Fizz Architecture 可以理解为 react-dom/server 渲染器的一种新特性,其主要目的在于加快服务端交付网页的速度,另一个也是为了在流式渲染的情况下支持 Suspense 组件 SSR 效果。
type
Post

后记

我会推荐你直接去看 Dan 的文章,讲的不能再好了。

正文

Basic Fizz Architecture 可以理解为 react-dom/server 渲染器的一种新特性,其主要目的在于加快服务端交付网页的速度,另一个也是为了在流式渲染的情况下支持 Suspense 组件 SSR 效果。
服务端进行 SSR 的最常见的问题在于我们的数据是异步的,也就是我们渲染过程肯定会被异步请求打断掉
现在常见的 SSR 方案:
  • 用户请求进来
  • 收集并请求组件依赖的异步数据
  • 注入到 Redux 中
  • 进行服务端渲染,得到生成的 html,渲染结束
  • 将已经得到数据注入到 html 中,返回 html
  • 浏览器接受,渲染 html
  • 将异步数据注入到 Redux 中
  • 进行调和
刚才说到 SSR 过程肯定会被异步请求打断掉的另一个意思是,我们必须事先请求数据,才能渲染出 HTML ,这意味着请求数据的那段时间就被浪费掉了,因为我们有些组件并不依赖异步数据,然而,我们却无法提前交付这些不依赖异步数据的组件。
究其原因是因为 React 自身没有提供获取数据的方式,现在 SSR 中异步方案都是由社区实现的,他们与 React 本身是割裂开的,由于 React 提供了 Suspense 作为数据方案,因此 React 就可以通过特殊处理 Suspense 组件来实现异步组件的 SSR ,当然这需要你通过 Suspense 来获取数据
新的 SSR 方案:
  • 用户请求进来
  • 直接进行服务端流式渲染 renderToNodeStream(),遇到 Suspense 组件,直接渲染 fallback (和现有行为一样),得到基本的 html,渲染没有结束
  • 将已经得到的 html 进行流式返回
  • 浏览器渲染基本 html,此时用户已经可以看到基本的界面了
  • 等到 Suspense 组件 Resolve ,将得到 html 通过 inline script 的形式流式传输到前端,替换掉当初对应的 fallback 的内容
  • 浏览器渲染 Suspense 组件
  • 重复继续这个行为直到不存在需要 Resolve 的组件
  • 进行调和
  • 浏览器渲染完毕
如果在渲染 Suspense 的时候发生了报错, React 会进行降级方案,不再渲染对应的 Suspense ,让客户端二次尝试渲染 Suspense 。
新架构的渲染模式非常类似 BigPipe 架构,这里就不多做介绍了,有兴趣的可以看我当初写的 ,总结下好处就是:
  • 服务端和浏览器可以并行进行渲染
  • 官方的异步解决方案,不需要通过约定的方式来声明 SSR 时需要的数据,只要正常的在组件内部请求数据即可,然后使用 Suspense 包裹起来
 

例子

const resource = fetchProfileData();

function ProfilePage() {
  return (
    <Suspense fallback={<div>Loading profile...</div>}>
      <ProfileDetails />
    </Suspense>
  );
}

function ProfileDetails() {
  // 尝试读取用户信息,尽管该数据可能尚未加载
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function App() {
	return (
		<div>
			<h1>App</h1>
			<ProfilePage />
		</div>
	);
}
当我们尝试做 renderToNodeStream(<App />) 的时候,我们得到一个渲染流,针对上述例子,这个渲染流首先吐出
<div>
	<h1>App</h1>
	<div id="unique_1">Loading profile...</div>
</div>
针对一次 SSR 渲染,渲染成 HTML 的时间远小于异步请求的时间,因此初始吐出 HTML 可以认为是近乎同步的,因此 TTFB , FP 和 FCP 的时间都会非常快,基本上可以认为请求刚到,就开始返回 HTML 内容了,按照以前的 SSR 逻辑,遇到 Suspense 组件会直接渲染 fallback 内容,Suspense 组件包裹的异步请求过程最终会发生在浏览器端。
对于新架构来说, React 尝试在服务端渲染 Suspense 组件,在等到其 Resolve 之后,接着往渲染流里面吐出内容
<h1 hidden id="unique_2">{user.name}</h1>
<script>replace('unique_1', 'unique_2')</script>
浏览器接解析渲染流吐出来的新内容,这时会将 Suspense fallback 的内容替换 Resolve 之后的内容,自此整个 SSR 过程结束。

思考

当着 Fizz 架构落地之后,在我们选择使用 Suspense 管理副作用后,我们不需要通过约定的方式来声明 SSR 时需要的数据,一切皆自然而然的发生,整个 React 的 SSR 架构会非常简单,我们只需要处理下浏览器和服务端路由映射相关的逻辑,其他的 react-dom/server 会全帮我们做好,不用再考虑如何将 Redux 的异步数据注入到 React 中,如何水合数据等问题。
 
It just all simply react !

参考


© 何云飞 2021 - 2024