前言
Suspense技术起初是为了加载异步组件,但逐渐应用到了所有异步过程中。其中,异步请求是Suspense最主要的使用场景。在不远的将来(React17/Vue3.1),基于Suspense的开发模式将成为主流,同时用户体验也会随着Suspense的使用得到优化。
Suspense的使用方式
Vue的使用方式
React的使用方式
Suspense原理
Suspense作为一个通用的技术,React和Vue在实现的思路上基本一致,核心原理有两方面:通信和渲染。
通信是指:Suspense与嵌入其内部的组件之间的通信,即Suspense如何获取组件的异步状态
渲染是指:Suspense如何控制嵌入其内部的组件的渲染
Suspense如何获取组件的异步状态
结论:通过“隐式”的获取组件异步状态所关联的promise。
表面上看来,不管是Vue还是React,我们都没有给Suspense传任何promise相关的props,那么Suspense到底是如何拿到的呢?我们固化的思维容易认为:props是通往组件的唯一入口。
实际上,Suspense作为一个框架内置的组件,它有“充分的自由”去获取任何运行时中存在的变量。
对于Vue来说,如果在setup中"return"了一个promise,那么Vue就能拿到这个promise,并自动传递给离这个组件最近的那个Suspense。
对于React来说,如果在render中"throw"了一个promise,那么React就能捕获到这个promise,并自动传递给离这个组件最近的那个Suspense。
这样,Suspense就能获取到组件的异步状态了。
Suspense如何控制嵌入其内部的组件的渲染
结论:预渲染嵌入的组件到一个隐藏的dom容器中,并在异步状态resolve之后把预渲染的组件移动到真实的dom容器中。
Suspense总会预渲染组件到一个隐藏的容器,之后它会根据所关联的promise的状态进行抉择:
如果promise处于pending状态,那么就会渲染fallback组件到真实的dom容器中
如果promise到达resolve状态,那么就会直接把预渲染好的组件移动到真实的dom容器中,并清除fallback组件
如果promise到达reject状态,那么框架会直接抛出reject的错误,后面可以被ErrorBoundary接收
第一阶段用户体验优化
在组件中加载异步数据是业务场景中最常见的需求,组件的状态会根据异步数据的状态不同而变化,通常是:loading状态 -> 真实内容。
使用传统的v-if/v-else,或者jsx中的三元表达式的形式,去渲染不同状态下对应的组件内容,实际会有性能问题:
// template
<Loading v-if="loading">loading...</Loading>
<Content v-else>真实内容</Content>
// jsx
(
loading ? <Loading>loading</Loading> : <Content>真实内容</Content>
)在切换渲染两种不同的element时,都伴随着组件的销毁与重建。而如果使用Suspense,真实内容和fallback这两者会同时存在,真实内容不会随着fallback出现而销毁,也不会随着fallback的消失而重建,相反真实内容只是在更新。
这样,用户体验就得到了第一阶段的优化,因为页面的渲染性能明显提升。
第二阶段用户体验优化(仅React)
渲染性能已经得到明显的提升,那么我们还能优化什么呢?React并发模式带来了新的思路:干掉loading状态。
React并发模式是React Fiber架构的应用,在React Fiber架构下,组件树的更新以组件为粒度被异步化,组件的更新可以被中断,从而不会阻塞主线程。
更多内容请看:reactjs.org/docs/concur…
实际环境中,用户的设备性能、网络速度各有不同,有的人性能好网速快,有的人性能差网速慢,结果就导致这种情况:
设备条件 | 使用体验 | 原因 |
|---|---|---|
高 | 低 | 设备条件已经这么好了,加载数据毫秒级别,但是还是要闪一下loading状态 |
低 | 高 | 设备条件不好,加载数据秒级别,有一个loading状态会很舒服 |
在React并发模式中结合使用Suspense,即可解决上述问题,这里有一个demo:
点击Refresh会更新列表
更新列表的接口耗时随机在0-1s之间
如果接口返回的时间在0.5s之内,则不会显示loading状态
如果接口返回的时间在0.5s之外,则会显示loading状态
FAQ
Wait,我不用React并发模式也能实现第二阶段用户体验优化
是的,我们完全可以自行实现这个功能,无非是加一个定时器,只有超过某个时间后,才会显示loading。但是,这样我们就享受不到Suspense预渲染带来的性能提升了。
Vue用户体验就一定不如React吗?
虽然Vue在Suspense上无法贡献更多性能优化,但是Vue会从另外一个方向:预分析vnode,根据类型不同给予相应的patchFlag,并根据flag的不同采取最高效的更新方式,带来显著的渲染性能提升。