- 发布于
 
Vue3的组件的更新过程
- Authors
 
- Name
 - 田中原
 
Vue3的组件的更新过程
目录
渲染的入口
接着上次挂载组件往下看packages/runtime-core/src/renderer.ts 的mountComponent 组件渲染实际上是调用了setupRenderEffect
setupRenderEffect 主要逻辑
- 声明了一个组件更新函数,
- 新
vnode的创建 - 新旧
vnode通过patch 
 - 新
 - 处理副作用,和
SchedulerJob- 通过
new ReactiveEffect创建effect实例, - 实例的
update函数加入effect.run 
 - 通过
 - 最后调用
update 
节点更新在patch 中的逻辑
const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    namespace = undefined,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren,
  ) => {
    if (n1 === n2) {
      // 新旧相同直接返回
      return
    }
    // patching & not same type, unmount old tree
    if (n1 && !isSameVNodeType(n1, n2)) {
      // n1有值是更新,并且不是相同类型,卸载旧树。如果更新不走这里就该走diff的流程
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      // n1改为null,后续会重新挂载
      n1 = null
    }
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }
    ...
   if (shapeFlag & ShapeFlags.ELEMENT) {
     // 处理成真实的元素
      processElement()
    } else if (shapeFlag & ShapeFlags.COMPONENT) {
     // 对组件处理
      processComponent()
  }
更新时对组件的处理
processComponent ⇒ updateComponent
主要判断当前组件的子组件是否需要更新 如果子组件也需要更新(子组件自己状态变化了),会从更新队列中删除子组件,手动更新子
  /**
   * @description 判断子组件是否需要更新
   * 如果需要则递归执行子组件的副作用渲染函数更新
   * 否则只更新vnode属性,让子组件实例保留对组件vnode的引用
   * 用于子组件自身数据变化重新渲染时,渲染函数能拿到父组件的vnode
   */
  const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
    const instance = (n2.component = n1.component)!
    // 根据新旧子组件的VNode,判断是否需要更新子组件
    if (shouldUpdateComponent(n1, n2, optimized)) {
      if (
        __FEATURE_SUSPENSE__ &&
        instance.asyncDep &&
        !instance.asyncResolved
      ) {
        // async & still pending(suspense未resolved) - 只更新props和slots,因为组件的渲染响应效果尚未设置
        // 更新组件上的信息,比如props、slots等
        updateComponentPreRender(instance, n2, optimized)
        return
      } else {
        instance.next = n2
        // 如果子组件也在队列中,删除它,以避免在同一次刷新中多次更新同一个子组件。
        // 避免子组件由于自身数据变化导致的重复更新。等于是把子组件的更新任务从队列中移除,手动更新了子组件
        invalidateJob(instance.update)
        // instance.update是响应式effect
        instance.effect.dirty = true
        // 执行子组件的副作用渲染函数,主动触发子组件的更新
        instance.update()
      }
    } else {
      // 不需要更新,只需复制属性,
      n2.el = n1.el
      instance.vnode = n2
    }
  }
对元素的处理
patchElement ⇒ patchChildren
最终的dom操作
- 通过
patchChildren处理子节点,主要是文本、数组、或无子节点 - 通过
patchProps处理dom上的属性 
/**
   *
   * @description 主要做两件事
   * 1. patchProps处理props
   * 2. patchChildren处理子节点
   */
  const patchElement = (
    n1: VNode,
    n2: VNode,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    namespace: ElementNamespace,
    slotScopeIds: string[] | null,
    optimized: boolean,
  ) => {
    const el = (n2.el = n1.el!)
    let { patchFlag, dynamicChildren, dirs } = n2
    patchFlag |= n1.patchFlag & PatchFlags.FULL_PROPS
    const oldProps = n1.props || EMPTY_OBJ
    const newProps = n2.props || EMPTY_OBJ
    let vnodeHook: VNodeHook | undefined | null
    // 有父组件的话,禁用递归
    parentComponent && toggleRecurse(parentComponent, false)
    if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
      // 有vnodeHook的话,调用vVnode的onVnodeBeforeUpdate
      invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
    }
    if (dirs) {
      // dirs,调用的beforeUpdate生命周期
      invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
    }
    parentComponent && toggleRecurse(parentComponent, true)
    ...
    // 没法优化、全量diff
    patchChildren()
    ... 省略成吨的逻辑判断
    patchProps()
    if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
      // dom处理完毕调用updated生命周期
      queuePostRenderEffect(() => {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
        dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
      }, parentSuspense)
    }
  }