Vue3 源码解析系列 - 挂载组件

564 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

前言

在 patch 过程,如果是组件的话会执行 mountComponent 方法进行挂载。这篇我们来看一下它的实现。

mountComponent

mountComponent 的位置在 packages/runtime-core/src/renderer.ts 文件里。

 const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 2.x compat may pre-create the component instance before actually
    // mounting
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    // 组件 -> 组件实例
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))


    // resolve props and slots for setup context
    if (!(__COMPAT__ && compatMountInstance)) {
      // 执行setup
      setupComponent(instance)
    }

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

mountComponent 里面主要调用了三个方法。

  1. createComponentInstance 创建组件实例,里面定义了一个组件的一些属性和方法,并进行返回
  2. setupComponent 组件初始化处理
  3. setupRenderEffect 创建 effect

setupComponent

在 setupComponent 中对组件实例做了一些初始化的工作,他的代码位置在 packages/runtime-core/src/component.ts

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

  const { props, children } = instance.vnode
  const isStateful = isStatefulComponent(instance)
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

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

我们来看下具体做了哪些工作。

  1. 使用 isStatefulComponent 方法,判断组件是否为有状态组件,在 isStatefulComponent 中其实是使用了我们之前讲过的 shapeFlag 来判断 如果 shapeFlag 与 ShapeFlags.STATEFUL_COMPONENT 进行位与操作是 true 时就是有状态组件
  2. 使用 initProps 方法对组件的 prop 进行初始化
  3. 使用 initSlots 方法对组件的插槽进行初始化。
  4. 执行 setupStatefulComponent 方法。

我们接下来再去看看 setupStatefulComponent 的实现

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

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

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

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

    if (isPromise(setupResult)) {
      setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

他主要做了这些工作。

  1. 从 Component 中解构出 setup 方法
  2. 判断 setup 方法是否存在
  3. 如果不存在则直接调用最后的 finishComponentSetup 进行处理
  4. 如果存在,根据setup参数的多少决定是否初始化instance.setupContext对象,中间调用了createSetupContext方法
  5. 通过 callWithErrorHandling 调用 setup 方法,并返回结果 setupResult
  6. 通过 handleSetupResult 方法最结果进行处理,因为我们在写setup 的时候,返回值可能有多种类型,所以要进行分别处理
  7. 处理完雨后,在handleSetupResult内部会调用 finishComponentSetup 方法。

小结

这篇了解了 mountComponent 里面的逻辑,知道了里面是如何处理属性、插槽、还有 setup 方法,并在最后进行调用 finishComponentSetup 方法,下一篇我们着重学习一下 finishComponentSetup的逻辑。