Vue3 源码系列 (九):异步组件 defineAsyncComponent 与 Suspense
前面几篇走完了createApp的流程,理清了diff算法的思路。现在回归到运行时的核心API上。在第一篇和第二篇中有解读过watch和computed,而本文则主要梳理异步组件的API。
一、defineAsyncComponent
用于定义异步组件。
1. defineAsyncComponent
入参source,可以是一个异步函数loader,也可以是一个包含有异步函数loader的对象options。当source为options时可以进行更细致的自定义,如推迟时间、异常处理、异常兜底组件、加载组件等。由于import()动态加载得到的是一个Promise,因此,loader常用来结合import()引入单文件组件来构成异步组件。
1 | export interface AsyncComponentOptions<T = any> { |
函数中主要是定义一个load函数,通过对结构source得到的loader进行异常处理(是否重试),对加载成功的结果进行校验并得到正常的加载结果。load函数在返回结果中的setup里调用。
1 | // defineComponent |
defineAsyncComponent的返回值是个经过defineComponent处理过的options。而options中的setup有着异步组件的渲染逻辑。主要是调用load,通过createInnerComp来创建加载成功的组件,createVNode来进行异常和加载中的渲染。
1 | export function defineAsyncComponent< |
2. createInnerComp
当加载成功时,调用createInnerComp根据加载得到的resolvedComp来创建内部组件。实际上还是createVNode来创建的。这里继承了外部组件的ref。
1 | function createInnerComp( |
二、Suspense
在vue3.2中引入的新特性之一,便是异步组件Suspense。
1. process
和KeepAlive类型,Suspense暴露一个类似组件的API。当检查到__isSuspense == true时,判定当前组件为<Suspense>,会调用process方法并被传入renderer内部进行渲染。process也会根据旧节点是否存在,选择 挂载 或者 对比新旧节点并更新。
1 | // props |
1.1 mountSuspense
首次加载组件时会进入挂载逻辑。通过mountSuspense来挂载异步组件。
1 | function mountSuspense( |
1.2 patchSuspense
patchSuspense 进行新旧节点的对比与更新。Suspense有多个分支如活跃、等待、降级等。
- 当前(旧节点)有等待分支
pendingBranch,根据新旧等待分支节点类型是否相同分别做处理; - 当前(旧节点)没有等待分支
pendingBranch,根据新的等待分支与当前(旧的)活跃分支节点类型是否相同分别进行处理。
同时也会考虑异步依赖和是否已经处于降级状态。
1 | function patchSuspense( |
1.3 setActiveBranch
setActiveBranch用来设置活跃分支。
1 | function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) { |
2. SuspenseBoundary
2.1 ts 类型
1 | export interface SuspenseBoundary { |
2.2 createSuspenseBoundary
使用createSuspenseBoundary来生成一个SuspenseBoundary类型的suspense。由上面的ts类型我们知道,这个suspense具有resolve, fallback, move, next, registerDep, unmount等方法。下面就一个个分析每个方法的主要功能。
2.2.1 suspense.resolve
在拿到期望的异步结果时,调用resolve来用等待分支替换掉当前的活跃分支,从而渲染期望的内容。
- 因此,首先就是要保证异步组件具有等待分支,且组件尚未被卸载(开发环境);
- 之后调用从
suspenseInternals中拿到的move函数将等待分支从离线容器移动到实际容器中,并将其设置为活跃分支; - 沿着
suspense.parent链向上查找,将所有副作用合并到最外层的未解决的Suspense中; - 如果向上查找时没有发现未解决的先代
Suspense,则处理当前Suspense的所有副作用,并将副作用列表清空; - 触发
Suspense的onResolve事件。
1 | const suspense: SuspenseBoundary = { |
2.2.2 suspense.fallback
fallback用于挂载降级内容Fallback。
- 触发
onFallback事件; - 有延迟动画
transition则在其afterLeave动画钩子中挂载Fallback内容; - 卸载当前活跃分支;
- 若没有延迟动画则直接挂载
Fallback内容。
1 | const suspense: SuspenseBoundary = { |
2.2.3 suspense.move
处理活跃分支和容器。
1 | // function createSuspenseBoundary |
2.2.4 suspense.next
next()递归取到supense的活跃分支链的末端。
1 | const suspense: SuspenseBoundary = { |
2.2.5 suspense.registerDep
registerDep用于注册依赖,接收一个实例instance和渲染副作用函数setupRenderEffect。若注册时,suspense处于等待期,则其异步依赖数量+1;随后注册实例instance上的异步依赖asyncDep,得到一个Promise。在then中使用handleSetupResult来处理异步依赖的解决结果,并执行渲染副作用setupRenderEffect;如果suspense处于等待期,则将其deps数量-1,因为一开始+1。
1 | const suspense: SuspenseBoundary = { |
2.2.6 unmount
卸载suspense:将suspense.isUnmounted置为true,卸载活跃分支和等待分支。
1 | const suspense: SuspenseBoundary = { |

