【真香系列】Vue-Next 源码第三章

1,152 阅读3分钟

目录

setupRenderEffect

上文提到 setupComponent 之后,会调用 setupRenderEffect 生成一个渲染的副作用方法。componentEffect 就是更新时会执行的方法,这里会执行组件的生命周期方法等,renderComponentRoot 会执行之前编译出来的 code 以生成 vnode 并且 normalizeVNode 会将 shapeFlag 变为 9,然后进行 processElement 操作。

// packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
  // create reactive effect for rendering
  instance.update = effect(function componentEffect() {
    if (!instance.isMounted) {
      let vnodeHook: VNodeHook | null | undefined
      const { el, props } = initialVNode
      const { bm, m, parent } = instance

      // beforeMount hook
      if (bm) {
        invokeArrayFns(bm)
      }
      // onVnodeBeforeMount
      if ((vnodeHook = props && props.onVnodeBeforeMount)) {
        invokeVNodeHook(vnodeHook, parent, initialVNode)
      }

      const subTree = (instance.subTree = renderComponentRoot(instance))

      if (el && hydrateNode) {

      } else {
        patch(
          null,
          subTree,
          container,
          anchor,
          instance,
          parentSuspense,
          isSVG
        )
        initialVNode.el = subTree.el
      }
      // mounted hook
      if (m) {
        queuePostRenderEffect(m, parentSuspense)
      }
      // onVnodeMounted
      if ((vnodeHook = props && props.onVnodeMounted)) {
        queuePostRenderEffect(() => {
          invokeVNodeHook(vnodeHook!, parent, initialVNode)
        }, parentSuspense)
      }
      // activated hook for keep-alive roots.
      // #1742 activated hook must be accessed after first render
      // since the hook may be injected by a child keep-alive
      const { a } = instance
      if (
        a &&
        initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
      ) {
        queuePostRenderEffect(a, parentSuspense)
      }
      instance.isMounted = true
    } else {
      // updateComponent 
    }
  }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}

effect 方法保证了在每次更新时一定会执行 componentEffect,后面会详细说明,先继续看主要流程。

patch

// packages/runtime-core/src/renderer.ts
const patch: PatchFn = (
  n1,
  n2,
  container,
  anchor = null,
  parentComponent = null,
  parentSuspense = null,
  isSVG = false,
  optimized = false
) => {
  const { type, ref, shapeFlag } = n2
  switch (type) {
    case Text:

    case Comment:

    case Static:

    case Fragment:

    default:
      if (shapeFlag & ShapeFlags.ELEMENT) {
        processElement(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
  }
}

processElement

初始化会执行 mountElement

// packages/runtime-core/src/renderer.ts
const processElement = (
  n1: VNode | null,
  n2: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) => {
  isSVG = isSVG || (n2.type as string) === 'svg'
  if (n1 == null) {
    mountElement(
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized
    )
  } else {
    patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  }
}

mountElement

// packages/runtime-core/src/renderer.ts
const mountElement = (
  vnode: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) => {
  let el: RendererElement
  let vnodeHook: VNodeHook | undefined | null
  const {
    type,
    props,
    shapeFlag,
    transition,
    scopeId,
    patchFlag,
    dirs
  } = vnode
  ...
  hostInsert(el, container, anchor)
}

还记得第一篇文章提到过的通过 createRenderer 创建平台相关的渲染器吗?参数 rendererOptions 包含了相关的 dom 操作,hostInsert 就是插入 dom 方法。该操作执行完后,组件完成挂载,渲染完成。

// packages/runtime-dom/src/nodeOps.ts
insert: (child, parent, anchor) => {
  parent.insertBefore(child, anchor || null)
}

effect

现在回头看 effect 方法,调用 createReactiveEffect 产生一个基于 componentEffectreactiveEffect 方法,首次加载页面会立即执行一次。

// packages/reactivity/src/effect.ts
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) {
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    effect()
  }
  return effect
}

createReactiveEffect

首先 effect 会先入栈,然后执行 fn(componentEffect) 时,执行的代码中会触发响应数据 get, get 会使用 track 进行依赖收集,后文会详细说明,当执行完 fn 最后会出栈。

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    if (!effectStack.includes(effect)) {
      cleanup(effect)
      try {
        enableTracking()
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}

总结

至此初始化流程已经全部完毕,其中较为重要的部分是通过 effect 进行了依赖收集,下一篇文章会介绍更新流程。

流程图

part2