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

1,288 阅读4分钟

目录

mount

mount 方法首先创建了 vnode,然后渲染 vnode,最后返回组件的代理对象。

// packages/runtime-core/src/apiCreateApp.ts
mount(rootContainer: HostElement, isHydrate?: boolean): any {
  if (!isMounted) {
    const vnode = createVNode(
      rootComponent as ConcreteComponent,
      rootProps
    )
    // store app context on the root VNode.
    // this will be set on the root instance on initial mount.
    vnode.appContext = context

    render(vnode, rootContainer)
    
    isMounted = true
    app._container = rootContainer
    // for devtools and telemetry
    ;(rootContainer as any).__vue_app__ = app

    return vnode.component!.proxy
  }
}

createVNode

// packages/runtime-core/src/vnode.ts
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {

  // class component normalization.
  if (isClassComponent(type)) {}

  // class & style normalization.
  if (props) {}

  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0

  const vnode: VNode = {
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }

  normalizeChildren(vnode, children)

  // normalize suspense children
  if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
    const { content, fallback } = normalizeSuspenseChildren(vnode)
    vnode.ssContent = content
    vnode.ssFallback = fallback
  }
  
  if (
    shouldTrack > 0 &&
    // avoid a block node from tracking itself
    !isBlockNode &&
    // has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.
    patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  return vnode
}

参数 type 是例子中的组件选项 {template: ..., setup() {...}},这里首次出现了 shapeFlag,意为 vnode 的类型,每种类型都有对应的处理方法,在 Vue3 中将 vnode 大致分为一下几类:

// packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
  ELEMENT = 1, // 元素 string
  FUNCTIONAL_COMPONENT = 1 << 1, // 2 function
  STATEFUL_COMPONENT = 1 << 2, // 4 object
  TEXT_CHILDREN = 1 << 3, // 8 文本
  ARRAY_CHILDREN = 1 << 4, // 16 数组
  SLOTS_CHILDREN = 1 << 5, // 32 插槽
  TELEPORT = 1 << 6, // 64 teleport
  SUSPENSE = 1 << 7, // 128 suspense
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,// 256 keep alive 组件
  COMPONENT_KEPT_ALIVE = 1 << 9, // 512 keep alive 组件
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 组件
}

这里选项是对象,所以 shapeFlag4_createVNode 中额外要提及的是 currentBlockpatchFlag,这两个跟新特性相关,后面的文章中会详细说明。

render

创建完 vnode 之后,调用实例上的 render 方法,进行第一次 patch 过程,最后 flushPostFlushCbs 执行了一次清空任务调度队列,任务调度会在后面详细说。

// packages/runtime-core/src/renderer.ts baseCreateRenderer
const render: RootRenderFunction = (vnode, container) => {
  if (vnode == null) {
    if (container._vnode) {
      unmount(container._vnode, null, null, true)
    }
  } else {
    patch(container._vnode || null, vnode, container)
  }
  flushPostFlushCbs()
  container._vnode = vnode
}

patch

前面得知 shapeFlag 为 4,所以会调用 processComponent 进行处理。

// 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) {

      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      } else if (shapeFlag & ShapeFlags.TELEPORT) {

      } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {

      }
  }
}

processComponent

这里因为是初始化,所以会直接调用 mountComponent 挂载组件。

// packages/runtime-core/src/renderer.ts
const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
  if (n1 == null) {
    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
    } else {
      mountComponent(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    }
  } else {
    updateComponent(n1, n2, optimized)
  }
}

mountComponent

首先 createComponentInstance 创建了组件实例,然后调用 setupComponent 去执行 setup 函数获取返回值,最后执行 setupRenderEffect 去定义渲染相关的 effectsetupComponentsetupRenderEffect 是初始化流程中相对比较重要的两个环节。

// packages/runtime-core/src/renderer.ts
const mountComponent: MountComponentFn = (
  initialVNode,
  container,
  anchor,
  parentComponent,
  parentSuspense,
  isSVG,
  optimized
) => {
  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
    initialVNode,
    parentComponent,
    parentSuspense
  ))
  
  setupComponent(instance)

  setupRenderEffect(
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  )
}

setupComponent

初始化 propsattrsslots,前面提到 shapeFlagSTATEFUL_COMPONENT,所以会通过 setupStatefulComponent 执行 setup

// packages/runtime-core/src/component.ts
export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children, shapeFlag } = instance.vnode
  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

setupStatefulComponent

callWithErrorHandling 执行了 setup, 返回结果 setupResult 传入 handleSetupResult 进行 proxy 包装并将 template 编译。

// packages/runtime-core/src/component.ts
function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  // 0. create render proxy property access cache
  instance.accessCache = {}
  // 1. create public instance / render proxy
  // also mark it raw so it's never observed
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)

  // 2. call setup()
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    currentInstance = null

    if (isPromise(setupResult)) {

    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

callWithErrorHandling

执行 setup

// packages/runtime-core/src/errorHandling.ts
export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  let res
  try {
    res = args ? fn(...args) : fn()
  } catch (err) {
    handleError(err, instance, type)
  }
  return res
}

handleSetupResult

proxyRefssetup 结果包装 Proxy,然后挂载到 instance.setupState

// packages/runtime-core/src/component.ts
export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  if (isFunction(setupResult)) {
    // setup returned bindings.
    // assuming a render function compiled from template is present.
    instance.render = setupResult as InternalRenderFunction
  } else if (isObject(setupResult)) {
    instance.setupState = proxyRefs(setupResult)
  }
  finishComponentSetup(instance, isSSR)
}

finishComponentSetup

编译模版

// packages/runtime-core/src/component.ts
function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  // template / render function normalization
  if (__NODE_JS__ && isSSR) {

  } else if (!instance.render) {
    // could be set from setup()
    if (compile && Component.template && !Component.render) {
      Component.render = compile(Component.template, {
        isCustomElement: instance.appContext.config.isCustomElement,
        delimiters: Component.delimiters
      })
    }

  }
}

compile

ast 是基于模版生成的抽象语法树。

// packages/compiler-core/src/compile.ts
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  ...
  
  const ast = isString(template) ? baseParse(template, options) : template
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
    prefixIdentifiers
  )
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
  )

  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}

generate 生成了编译代码

"const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue

    return (_openBlock(), _createBlock("h1", null, _toDisplayString(state.message), 1 /* TEXT */))
  }
}"

总结

初始化流程至此,主要分为:

  1. 创建了 vnode 节点
  2. 执行 setup
  3. 编译生成渲染函数

之后将会通过 setupRenderEffect 为渲染创建响应副作用 reactive effect,也就是每次渲染时都会执行的函数。

流程图

part1