Vue3源码系列 (九):异步组件 defineAsyncComponent 与 Suspense

1,751 阅读11分钟

前面几篇走完了createApp的流程,理清了diff算法的思路。现在回归到运行时的核心API上。在第一篇和第二篇中有解读过watchcomputed,而本文则主要梳理异步组件的API

一、defineAsyncComponent

用于定义异步组件。

1. defineAsyncComponent

入参source,可以是一个异步函数loader,也可以是一个包含有异步函数loader的对象options。当sourceoptions时可以进行更细致的自定义,如推迟时间、异常处理、异常兜底组件、加载组件等。由于import()动态加载得到的是一个Promise,因此,loader常用来结合import()引入单文件组件来构成异步组件。

export interface AsyncComponentOptions<T = any> {
  loader: AsyncComponentLoader<T>
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}

函数中主要是定义一个load函数,通过对结构source得到的loader进行异常处理(是否重试),对加载成功的结果进行校验并得到正常的加载结果。load函数在返回结果中的setup里调用。

// defineComponent
export function defineComponent(options: unknown) {
  return isFunction(options) ? { setup: options, name: options.name } : options
}
​
// defineAsyncComponent
export function defineAsyncComponent<
  T extends Component = { new (): ComponentPublicInstance }
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
  
  // 如果source本身是个函数,则包装成有loader的对象,方便后续统一处理
  if (isFunction(source)) {
    source = { loader: source }
  }
​
  // 解构source
  const {
    loader,
    loadingComponent,
    errorComponent,
    delay = 200,
    timeout, // undefined = never times out
    suspensible = true,
    onError: userOnError
  } = source
​
  let pendingRequest: Promise<ConcreteComponent> | null = null
  let resolvedComp: ConcreteComponent | undefined
​
  // 定义函数:失败重试
  let retries = 0
  const retry = () => {
    retries++
    pendingRequest = null
    return load()
  }
​
  // 定义加载函数
  const load = (): Promise<ConcreteComponent> => {
    let thisRequest: Promise<ConcreteComponent>
    return (
      pendingRequest ||
      (thisRequest = pendingRequest =
       // loader异步加载
        loader()
          // 处理加载异常
          .catch(err => {
            err = err instanceof Error ? err : new Error(String(err))
            // 有userOnError时,失败重试
            if (userOnError) {
              return new Promise((resolve, reject) => {
                const userRetry = () => resolve(retry())
                const userFail = () => reject(err)
                userOnError(err, userRetry, userFail, retries + 1)
              })
            // 否则 失败抛错
            } else {
              throw err
            }
          })
          // 加载成功
          .then((comp: any) => {
            if (thisRequest !== pendingRequest && pendingRequest) {
              return pendingRequest
            }
            // 没有comp告警
            if (__DEV__ && !comp) {
              warn(
                `Async component loader resolved to undefined. ` +
                  `If you are using retry(), make sure to return its return value.`
              )
            }
            // 处理 es 模块
            // interop module default
            if (
              comp &&
              (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
            ) {
              comp = comp.default
            }
            // comp 必须是对象或函数
            if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
              throw new Error(`Invalid async component load result: ${comp}`)
            }
            // 得到 resolveComponent
            resolvedComp = comp
            return comp
          }))
    )
  }
  
  return // ... 这里暂时省略返回值
}

defineAsyncComponent的返回值是个经过defineComponent处理过的options。而options中的setup有着异步组件的渲染逻辑。主要是调用load,通过createInnerComp来创建加载成功的组件,createVNode来进行异常和加载中的渲染。

export function defineAsyncComponent<
  T extends Component = { new (): ComponentPublicInstance }
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
  if (isFunction(source)) {
    source = { loader: source }
  }
​
  const {
    loader,
    loadingComponent,
    errorComponent,
    delay = 200,
    timeout, // undefined = never times out
    suspensible = true,
    onError: userOnError
  } = source
  
  // ...
  
  return defineComponent({
    name: 'AsyncComponentWrapper',
​
    __asyncLoader: load,
​
    get __asyncResolved() {
      return resolvedComp
    },
​
    setup() {
      const instance = currentInstance!
​
      // 已加载完成的case
      // already resolved
      if (resolvedComp) {
        return () => createInnerComp(resolvedComp!, instance)
      }
​
      // 定义错误处理程序
      const onError = (err: Error) => {
        pendingRequest = null
        handleError(
          err,
          instance,
          ErrorCodes.ASYNC_COMPONENT_LOADER,
          !errorComponent /* do not throw in dev if user provided error component */
        )
      }
​
      // Suspense 或者 SSR:加载并返回
      // suspense-controlled or SSR.
      if (
        (__FEATURE_SUSPENSE__ && suspensible && instance.suspense) ||
        (__SSR__ && isInSSRComponentSetup)
      ) {
        return load()
          .then(comp => {
            return () => createInnerComp(comp, instance)
          })
          .catch(err => {
            onError(err)
            return () =>
              // 异常兜底组件
              errorComponent
                ? createVNode(errorComponent as ConcreteComponent, {
                    error: err
                  })
                : null
          })
      }
​
      // 状态初始化
      const loaded = ref(false)
      const error = ref()
      const delayed = ref(!!delay)
​
      // 采用定时器计时
      if (delay) {
        setTimeout(() => {
          delayed.value = false
        }, delay)
      }
​
      // 既没有拿到结果,又没有异常,则超时处理
      if (timeout != null) {
        setTimeout(() => {
          if (!loaded.value && !error.value) {
            const err = new Error(
              `Async component timed out after ${timeout}ms.`
            )
            onError(err)
            error.value = err
          }
        }, timeout)
      }
​
      // 加载
      load()
        .then(() => {
          loaded.value = true
          // 对于KeepAlive的内容,加载成功后进行强制更新
          if (instance.parent && isKeepAlive(instance.parent.vnode)) {
            // parent is keep-alive, force update so the loaded component's
            // name is taken into account
            queueJob(instance.parent.update)
          }
        })
        .catch(err => {
          onError(err)
          error.value = err
        })
​
      return () => {
        // 加载成功
        if (loaded.value && resolvedComp) {
          return createInnerComp(resolvedComp, instance)
        // 异常
        } else if (error.value && errorComponent) {
          return createVNode(errorComponent as ConcreteComponent, {
            error: error.value
          })
        // 加载中
        } else if (loadingComponent && !delayed.value) {
          return createVNode(loadingComponent as ConcreteComponent)
        }
      }
    }
  }) as T
  
}

2. createInnerComp

当加载成功时,调用createInnerComp根据加载得到的resolvedComp来创建内部组件。实际上还是createVNode来创建的。这里继承了外部组件的ref

function createInnerComp(
  comp: ConcreteComponent,
  {
    vnode: { ref, props, children, shapeFlag },
    parent
  }: ComponentInternalInstance
) {
  const vnode = createVNode(comp, props, children)
  // ensure inner component inherits the async wrapper's ref owner
  vnode.ref = ref
  return vnode
}

二、Suspense

vue3.2中引入的新特性之一,便是异步组件Suspense

1. process

KeepAlive类型,Suspense暴露一个类似组件的API。当检查到__isSuspense == true时,判定当前组件为<Suspense>,会调用process方法并被传入renderer内部进行渲染。process也会根据旧节点是否存在,选择 挂载 或者 对比新旧节点并更新。

// props
export interface SuspenseProps {
  onResolve?: () => void
  onPending?: () => void
  onFallback?: () => void
  timeout?: string | number
}
​
// Suspense
export const SuspenseImpl = {
  name: 'Suspense',
  // In order to make Suspense tree-shakable, we need to avoid importing it
  // directly in the renderer. The renderer checks for the __isSuspense flag
  // on a vnode's type and calls the `process` method, passing in renderer
  // internals.
  __isSuspense: true,
  process(
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean,
    // platform-specific impl passed from renderer
    rendererInternals: RendererInternals
  ) {
    if (n1 == null) {
      mountSuspense(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized,
        rendererInternals
      )
    } else {
      patchSuspense(
        n1,
        n2,
        container,
        anchor,
        parentComponent,
        isSVG,
        slotScopeIds,
        optimized,
        rendererInternals
      )
    }
  },
  hydrate: hydrateSuspense,
  create: createSuspenseBoundary,
  normalize: normalizeSuspenseChildren
}
​
// Force-casted public typing for h and TSX props inference
export const Suspense = (__FEATURE_SUSPENSE__ ? SuspenseImpl : null) as any as {
  __isSuspense: true
  new (): { $props: VNodeProps & SuspenseProps }
}

1.1 mountSuspense

首次加载组件时会进入挂载逻辑。通过mountSuspense来挂载异步组件。

function mountSuspense(
  vnode: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean,
  rendererInternals: RendererInternals
) {
  const {
    p: patch,
    o: { createElement }
  } = rendererInternals
  // 创建一个 div 作为容器,尚未加入到文档中
  const hiddenContainer = createElement('div')
  // 创建 suspense
  const suspense = (vnode.suspense = createSuspenseBoundary(
    vnode,
    parentSuspense,
    parentComponent,
    container,
    hiddenContainer,
    anchor,
    isSVG,
    slotScopeIds,
    optimized,
    rendererInternals
  ))
​
  // 旧节点为 null ,挂载
  // start mounting the content subtree in an off-dom container
  patch(
    null,
    (suspense.pendingBranch = vnode.ssContent!),
    hiddenContainer,
    null,
    parentComponent,
    suspense,
    isSVG,
    slotScopeIds
  )
  
  // 检查异步依赖
  // now check if we have encountered any async deps
  if (suspense.deps > 0) {
    // has async
    // invoke @fallback event
    triggerEvent(vnode, 'onPending')
    triggerEvent(vnode, 'onFallback')
​
    // 有异步依赖,先降级
    // mount the fallback tree
    patch(
      null,
      vnode.ssFallback!,
      container,
      anchor,
      parentComponent,
      null, // fallback tree will not have suspense context
      isSVG,
      slotScopeIds
    )
    // 活跃分支
    setActiveBranch(suspense, vnode.ssFallback!)
  } else {
    // 没有异步依赖
    // Suspense has no async deps. Just resolve.
    suspense.resolve()
  }
}

1.2 patchSuspense

patchSuspense 进行新旧节点的对比与更新。Suspense有多个分支如活跃、等待、降级等。

  • 当前(旧节点)有等待分支pendingBranch,根据新旧等待分支节点类型是否相同分别做处理;
  • 当前(旧节点)没有等待分支pendingBranch,根据新的等待分支与当前(旧的)活跃分支节点类型是否相同分别进行处理。

同时也会考虑异步依赖和是否已经处于降级状态。

function patchSuspense(
  n1: VNode,
  n2: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean,
  { p: patch, um: unmount, o: { createElement } }: RendererInternals
) {
  // suspense复用
  const suspense = (n2.suspense = n1.suspense)!
  // 虚拟DOM更新
  suspense.vnode = n2
  // DOM复用
  n2.el = n1.el
  const newBranch = n2.ssContent!
  const newFallback = n2.ssFallback!
​
  // 解构一份备用
  const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
  // 如果当前有等待分支
  if (pendingBranch) {
    // 刷新等待分支的内容
    suspense.pendingBranch = newBranch
    // 新旧等待分支属于同样的节点类型
    if (isSameVNodeType(newBranch, pendingBranch)) {
      // patch 做更详细的比较
      // same root type but content may have changed.
      patch(
        pendingBranch,
        newBranch,
        suspense.hiddenContainer,
        null,
        parentComponent,
        suspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      // 没有异步依赖,则直接resolve
      if (suspense.deps <= 0) {
        suspense.resolve()
      // 有异步依赖且处于 降级 状态
      } else if (isInFallback) {
        patch(
          activeBranch,
          newFallback,
          container,
          anchor,
          parentComponent,
          null, // fallback tree will not have suspense context
          isSVG,
          slotScopeIds,
          optimized
        )
        setActiveBranch(suspense, newFallback)
      }
    } else {  // 新旧等待分支不属于同一类型的节点
      // toggled before pending tree is resolved
      suspense.pendingId++
      // 处理旧节点
      if (isHydrating) {
        // if toggled before hydration is finished, the current DOM tree is
        // no longer valid. set it as the active branch so it will be unmounted
        // when resolved
        suspense.isHydrating = false
        suspense.activeBranch = pendingBranch
      } else {
        unmount(pendingBranch, parentComponent, suspense)
      }
      // 状态重置
      // increment pending ID. this is used to invalidate async callbacks
      // reset suspense state
      suspense.deps = 0
      // discard effects from pending branch
      suspense.effects.length = 0
      // discard previous container
      suspense.hiddenContainer = createElement('div')
​
      // 处理降级状态
      if (isInFallback) {
        // 挂载等待分支
        // already in fallback state
        patch(
          null,
          newBranch,
          suspense.hiddenContainer,
          null,
          parentComponent,
          suspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        // 没有异步依赖,则resolve
        if (suspense.deps <= 0) {
          suspense.resolve()
        } else {
          // 对比旧的活跃分支与新的降级分支
          patch(
            activeBranch,
            newFallback,
            container,
            anchor,
            parentComponent,
            null, // fallback tree will not have suspense context
            isSVG,
            slotScopeIds,
            optimized
          )
          // 重设活跃分支
          setActiveBranch(suspense, newFallback)
        }
      
      // 不处于 降级状态,且活跃分支与新的等待分支节点类型一致
      } else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
        // 对比当前活跃分支与新的等待分支
        // toggled "back" to current active branch
        patch(
          activeBranch,
          newBranch,
          container,
          anchor,
          parentComponent,
          suspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        // 强制resolve
        // force resolve
        suspense.resolve(true)
      } else {
        // 不处于 降级状态,且新的等待分支与当前活跃分支节点类型不一致
        // 挂载新的等待分支
        // switched to a 3rd branch
        patch(
          null,
          newBranch,
          suspense.hiddenContainer,
          null,
          parentComponent,
          suspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        // 没有异步依赖则 resolve
        if (suspense.deps <= 0) {
          suspense.resolve()
        }
      }
    }
  // 当前没有等待的分支
  } else {
    // 新的等待分支与当前活跃分支节点类型一致,则通过patch来对比更新
    if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
      // root did not change, just normal patch
      patch(
        activeBranch,
        newBranch,
        container,
        anchor,
        parentComponent,
        suspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      // 重设活跃分支
      setActiveBranch(suspense, newBranch)
    } else {  // 新的等待分支与当前活跃分支节点类型不同,挂载新的等待分支
      // root node toggled
      // invoke @pending event
      triggerEvent(n2, 'onPending')
      // mount pending branch in off-dom container
      suspense.pendingBranch = newBranch
      suspense.pendingId++
      patch(
        null,
        newBranch,
        suspense.hiddenContainer,
        null,
        parentComponent,
        suspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      // 没有异步依赖则 resolve
      if (suspense.deps <= 0) {
        // incoming branch has no async deps, resolve now.
        suspense.resolve()
      } else {  // 有异步依赖
        const { timeout, pendingId } = suspense
        // 有设置超时时间,定时器 超时降级
        if (timeout > 0) {
          setTimeout(() => {
            if (suspense.pendingId === pendingId) {
              suspense.fallback(newFallback)
            }
          }, timeout)
        } else if (timeout === 0) {
          //超时时间设置为0,即时降级
          suspense.fallback(newFallback)
        }
      }
    }
  }
}

1.3 setActiveBranch

setActiveBranch用来设置活跃分支。

function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) {
  suspense.activeBranch = branch
  const { vnode, parentComponent } = suspense
  const el = (vnode.el = branch.el)
  // in case suspense is the root node of a component,
  // recursively update the HOC el
  if (parentComponent && parentComponent.subTree === vnode) {
    parentComponent.vnode.el = el
    updateHOCHostEl(parentComponent, el)
  }
}

2. SuspenseBoundary

2.1 ts 类型

export interface SuspenseBoundary {
  vnode: VNode<RendererNode, RendererElement, SuspenseProps>
  parent: SuspenseBoundary | null
  parentComponent: ComponentInternalInstance | null
  isSVG: boolean
  container: RendererElement
  hiddenContainer: RendererElement
  anchor: RendererNode | null
  // 活跃分支
  activeBranch: VNode | null
  // 等待分支
  pendingBranch: VNode | null
  // 异步依赖数量
  deps: number
  pendingId: number
  timeout: number
  isInFallback: boolean
  isHydrating: boolean
  isUnmounted: boolean
  // 副作用
  effects: Function[]
  resolve(force?: boolean): void
  // 降级
  fallback(fallbackVNode: VNode): void
  move(
    container: RendererElement,
    anchor: RendererNode | null,
    type: MoveType
  ): void
  next(): RendererNode | null
  registerDep(
    instance: ComponentInternalInstance,
    setupRenderEffect: SetupRenderEffectFn
  ): void
  // 卸载
  unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
}

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的所有副作用,并将副作用列表清空;
  • 触发SuspenseonResolve事件。
const suspense: SuspenseBoundary = {
  
  // ...
  
  resolve(resume = false) {
    // 确保有等待分支且Suspense未被卸载
    if (__DEV__) {
      if (!resume && !suspense.pendingBranch) {
        throw new Error(
          `suspense.resolve() is called without a pending branch.`
        )
      }
      if (suspense.isUnmounted) {
        throw new Error(
          `suspense.resolve() is called on an already unmounted suspense boundary.`
        )
      }
    }
    // 解构变量
    const {
      vnode,
      activeBranch,
      pendingBranch,
      pendingId,
      effects,
      parentComponent,
      container
    } = suspense
​
    if (suspense.isHydrating) {
      suspense.isHydrating = false
    } else if (!resume) {
      // 有transition的延迟切入,则在其transition.afterLeave中进行move
      // move的作用是将等待分支的内容从离线容器移动到实际容器中
      const delayEnter =
        activeBranch &&
        pendingBranch!.transition &&
        pendingBranch!.transition.mode === 'out-in'
      if (delayEnter) {
        activeBranch!.transition!.afterLeave = () => {
          if (pendingId === suspense.pendingId) {
            move(pendingBranch!, container, anchor, MoveType.ENTER)
          }
        }
      }
      // this is initial anchor on mount
      let { anchor } = suspense
      // 卸载当前的活跃分支
      // unmount current active tree
      if (activeBranch) {
        // if the fallback tree was mounted, it may have been moved
        // as part of a parent suspense. get the latest anchor for insertion
        anchor = next(activeBranch)
        unmount(activeBranch, parentComponent, suspense, true)
      }
      // 没有transition的延迟切入,则在此move
      if (!delayEnter) {
        // move content from off-dom container to actual container
        move(pendingBranch!, container, anchor, MoveType.ENTER)
      }
    }
​
    // 将等待分支设置为活跃分支
    setActiveBranch(suspense, pendingBranch!)
    // 由于等待分支已经成为了活跃分支,因此置空等待分支,不必渲染降级内容
    suspense.pendingBranch = null
    suspense.isInFallback = false
​
    // flush buffered effects
    // check if there is a pending parent suspense
    let parent = suspense.parent
    let hasUnresolvedAncestor = false
    // 向上查找Suspense,将所有未执行的副作用合并到最外层的处于等待期的Suspense中
    // 这样就能等所有的Suspense都解决之后,再一并执行副作用
    while (parent) {
      if (parent.pendingBranch) {
        // found a pending parent suspense, merge buffered post jobs
        // into that parent
        parent.effects.push(...effects)
        // 标记尚有未解决的先代异步组件
        hasUnresolvedAncestor = true
        break
      }
      parent = parent.parent
    }
    // 向上查找的Suspense链没有未解决的,则处理副作用并清空副作用列表
    // no pending parent suspense, flush all jobs
    if (!hasUnresolvedAncestor) {
      queuePostFlushCb(effects)
    }
    suspense.effects = []
​
    // 触发异步组件的onResolve事件
    // invoke @resolve event
    triggerEvent(vnode, 'onResolve')
  },
  
  // ...
}
2.2.2 suspense.fallback

**fallback**用于挂载降级内容Fallback

  • 触发onFallback事件;
  • 有延迟动画transition则在其afterLeave动画钩子中挂载Fallback内容;
  • 卸载当前活跃分支;
  • 若没有延迟动画则直接挂载Fallback内容。
const suspense: SuspenseBoundary = {
  
  // ...
  
  fallback(fallbackVNode) {
    // 没有等待分支则返回
    if (!suspense.pendingBranch) {
      return
    }
​
    // 解构变量
    const { vnode, activeBranch, parentComponent, container, isSVG } =
      suspense
​
    // 触发onFallback
    // invoke @fallback event
    triggerEvent(vnode, 'onFallback')
​
    // 切换了锚点
    const anchor = next(activeBranch!)
    // 函数:挂载降级内容
    const mountFallback = () => {
      if (!suspense.isInFallback) {
        return
      }
      // mount the fallback tree
      patch(
        null,
        fallbackVNode,
        container,
        anchor,
        parentComponent,
        null, // fallback tree will not have suspense context
        isSVG,
        slotScopeIds,
        optimized
      )
      setActiveBranch(suspense, fallbackVNode)
    }
​
    // transition延迟动画
    const delayEnter =
      fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
    // 有延迟动画则在afterLeave动画钩子中挂载降级内容
    if (delayEnter) {
      activeBranch!.transition!.afterLeave = mountFallback
    }
    suspense.isInFallback = true
​
    // 卸载活跃分支
    // unmount current active branch
    unmount(
      activeBranch!,
      parentComponent,
      null, // no suspense so unmount hooks fire now
      true // shouldRemove
    )
​
    // 没有延迟动画则直接挂载降级内容
    if (!delayEnter) {
      mountFallback()
    }
  },
  
  //...
}
2.2.3 suspense.move

处理活跃分支和容器。

// function createSuspenseBoundary
const {
    p: patch,
    // 这个move想下面的suspense.move里用到的
    m: move,
    um: unmount,
    n: next,
    o: { parentNode, remove }
  } = rendererInternals
​
const suspense: SuspenseBoundary = {
  // ...
  
  move(container, anchor, type) {
    suspense.activeBranch &&
      // 这个move不是suspense.move,而是在 createSuspenseBoundary 函数中解构 rendererInternals 得到的
      move(suspense.activeBranch, container, anchor, type)
    suspense.container = container
  },
  
  // ...
}
2.2.4 suspense.next

next()递归取到supense的活跃分支链的末端。

const suspense: SuspenseBoundary = {
  // ...
  next() {
    return suspense.activeBranch && next(suspense.activeBranch)
  },
}
2.2.5 suspense.registerDep

registerDep用于注册依赖,接收一个实例instance和渲染副作用函数setupRenderEffect。若注册时,suspense处于等待期,则其异步依赖数量+1;随后注册实例instance上的异步依赖asyncDep,得到一个Promise。在then中使用handleSetupResult来处理异步依赖的解决结果,并执行渲染副作用setupRenderEffect;如果suspense处于等待期,则将其deps数量-1,因为一开始+1

const suspense: SuspenseBoundary = {
  // ...
  registerDep(instance, setupRenderEffect) {
    // 等待期 异步依赖数量+1
    const isInPendingSuspense = !!suspense.pendingBranch
    if (isInPendingSuspense) {
      suspense.deps++
    }
    const hydratedEl = instance.vnode.el
    // 注册异步依赖
    instance
      .asyncDep!.catch(err => {
        handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
      })
      .then(asyncSetupResult => {
        // retry when the setup() promise resolves.
        // component may have been unmounted before resolve.
        if (
          instance.isUnmounted ||
          suspense.isUnmounted ||
          suspense.pendingId !== instance.suspenseId
        ) {
          return
        }
        // retry from this component
        instance.asyncResolved = true
        const { vnode } = instance
        if (__DEV__) {
          pushWarningContext(vnode)
        }
        // 处理异步依赖运行的结果
        handleSetupResult(instance, asyncSetupResult, false)
        if (hydratedEl) {
          // vnode may have been replaced if an update happened before the
          // async dep is resolved.
          vnode.el = hydratedEl
        }
        const placeholder = !hydratedEl && instance.subTree.el
        // 调用传入的setupRenderEffect处理渲染副作用
        setupRenderEffect(
          instance,
          vnode,
          // component may have been moved before resolve.
          // if this is not a hydration, instance.subTree will be the comment
          // placeholder.
          parentNode(hydratedEl || instance.subTree.el!)!,
          // anchor will not be used if this is hydration, so only need to
          // consider the comment placeholder case.
          hydratedEl ? null : next(instance.subTree),
          suspense,
          isSVG,
          optimized
        )
        if (placeholder) {
          remove(placeholder)
        }
        updateHOCHostEl(instance, vnode.el)
        if (__DEV__) {
          popWarningContext()
        }
        // 异步依赖已解决,若suspense还在等待期,则异步依赖数量-1,当归零时调用resolve
        // only decrease deps count if suspense is not already resolved
        if (isInPendingSuspense && --suspense.deps === 0) {
          suspense.resolve()
        }
      })
  },
}
2.2.6 unmount

卸载suspense:将suspense.isUnmounted置为true,卸载活跃分支和等待分支。

const suspense: SuspenseBoundary = {
  // ...
  unmount(parentSuspense, doRemove) {
    suspense.isUnmounted = true
    if (suspense.activeBranch) {
      // 从suspenseInternals中拿到的unmount
      unmount(
        suspense.activeBranch,
        parentComponent,
        parentSuspense,
        doRemove
      )
    }
    if (suspense.pendingBranch) {
      // 从suspenseInternals中拿到的unmount
      unmount(
        suspense.pendingBranch,
        parentComponent,
        parentSuspense,
        doRemove
      )
    }
  }
}