React Conf 2021 (5) - Live Streaming with React Server Componet

270 阅读4分钟

本系列是在观看React Conf的随笔记录, 一共六篇。这几天整理了下来,内容全来自每个React Conf的Speaker(感谢🙏). 这其中也包含一些自己的想法。若有错误请评论区指出。 当然还是建议亲自去看一下新鲜出炉的React Conf 2021 - Replay

先来复习本章节一定要理解的两个概念 1.png Client Side Rendering: Load HTML → Load JS → Fetch Data → Render Components → Interactive

2.png Server Side Rendering: Fetch Data → Render Data in Template → Load JS → Hydrate

现有的Server rendering存在的问题是要求所有的组件步调一致。来看一个case:

() =>{
    return (
        <Layout>
            <NavBar />
            <SideBar />
            <RightPane>
                <Posts />
                <Comments />
            </RightPane>
        </Layout>
    )
}

3.png

这个页面中,假设comments组件需要从网络fetch数据,会很耗时。那么在现有Server Rendering的架构下会导致, Comments组件完成渲染耗时较久(因为需要从api拿数据), 那么整个页面的Render过程就会被拖慢,进而影响用户体验。这是当前架构中遇到的第一个问题:

Fetch everything, Show anything.

4.png

当然这样的问题下,我们会尝试继续往下走, 那么此时有两个选择:

  1. 等待所有的数据返回, 数据返回后Server Side渲染HTML

  2. 暂时不渲染Comment这个组件,把其他部分先返回给Client.

    1. 但是这样存在两个问题

      1. Comments作为应用中较为重要的一部分,我们希望用户在一开始就能看到
      2. 逃不开fetch数据的事实,如果comment 的api确实速度比较慢,那么只是把Server端等待的时间放到了Client端

暂时先放一下这个问题, 继续往前走。下一步是注水(hyrate). 从Server端过来的只是单纯的html文件。单纯的HTML并不存在事件交互,没有完成事件绑定。这就引出了第二个问题,在展示给用户页面之前, 需要加载所有的JS,完成注水。注水完成后,页面才会响应用户的交互。

Load everything, Hydrate anything.

5.png

那么此时假设, Comment模块包含大量的JS(参考一些博客的Comments第三方插件),那么注水的过程也会成为耗时点。

这也引入了第三个问题,在注水的过程中,没办法执行任何操作。

Hydrate everything, interact with anything. 1.png 所以有些页面中,用户在一开始的一两秒去点击菜单或者button的时候,没办法及时响应。


这一切都是React 18之前我们会遇到的问题,这是因为Server Rendering的步骤如下:

Server Side Rendering: Fetch Data → Render Data in Template → Load JS → Hydrate

2.png 必须要保持所有的步调一致,其中的任何一环出现了耗时增加,都会导致整个Server Render耗时增加。

而在React 18,我们可以把这些工作分解,或者说异步的执行不同代码的渲染任务。这有助于提高渲染效率,并且这一切都是自动发生的,不需要用户去编写这部分的代码。

<NavBar /> -> Fetch DataRender Data in TemplateLoad JSHydrate
<Post /> -> Fetch DataRender Data in TemplateLoad JSHydrate
<Comments /> -> Fetch DataRender Data in TemplateLoad JSHydrate

3.png 那么具体如何做呢? 使用Suspense包装一个组件,在自组件没完成之前,都会使用fallback代替展示。

() =>{ 
    return (
        <Suspense>
           <AnyComponent /> 
        </Suspense>
    )
}

Suspense组件给Server Rendering带来了两个特性:

  • Streaming html
  • Selective Hydration 4.png 那么现在让我们回到刚才上面提到的三个问题。
  1. Fetch everything, Show anything.
  2. Load everything, Hydrate anything.
  3. Hydrate everything, interact with anything.

先来看第一个,Server Side不得不等待某个组件的API返回后再把整个HTML送给Client端。

我们可以使用Suspense包裹哪个比较大的组件。来优化用户的首屏体验, 如果这个组件还没准备好, 我们会显示fallback里的占位组件渲染给用户,一般是骨架屏或者Spinner. 5.png

() =>{
    return (
        <Layout>
            <NavBar />
            <SideBar />
            <RightPane>
                <Posts />
                <Suspense fallback={<Spinner />}>
                    <Comments /> 
                </Suspense>
            </RightPane>
        </Layout>
    )
}

6.png 在接下来如果Server端,API响应后, 此时会生成Comment的HTML, 通过Streaming HTML送到前端。 第一个问题解决。

再来看看第二个问题,注水阶段如果存在较大的JS需要加载,耗时增加。

在React 18中,注水的过程也不需要等待Comments组件的注水完成。用户此时点击菜单部分是可以得到响应的。

7.png

那么假设我们的组件有多个耗时的组件呢?

() =>{
    return (
        <Layout>
            <NavBar />
            <SideBar />
            <RightPane>
                <Suspense fallback={<Spinner />}>
                    <Posts />
                </Suspense>
                <Suspense fallback={<Spinner />}>
                    <Comments /> 
                </Suspense>
            </RightPane>
        </Layout>
    )
}

8.png

9.png

我们可以使用多个Suspense标签分别的包裹他们, React会负责异步的处理他们。

另外有个特性是优先级,例如Post和Comments组件都是耗时的组件,那么如果用户点击了Comments, 那么React会优先完成Comments的注水,此时Comments组件得到了及时响应,这种方式给到用户的感觉就是实时响应。 10.png 这样, 第二个问题和第三个问题也就都解决了。不必等到所有的模块注水完成,而是优先解决用户关注的部分。