[Vue3源码剖析(二)]组件实例初始化过程

66 阅读3分钟

问题

通过本章源码的阅读可以解决以下三个问题:

  • 问题一:为什么对props解构后,就变成非响应式的?
  • 问题二:传入setup参数中,分别是props和ctx,它们是如何传递进来的?
  • 问题三:setup的执行时刻?为什么setup中没有created钩子?
  • 问题四:如果setup函数的返回值和data这些数据发生冲突,vue3会如何处理?

源码

我们知道初始化组件时,首次是走processComponent中的mountComponent

  1. createComponentInstance:instance实例被创建,但是所有api都为null
  2. setupComponent:对组件实例的props/slot/data等进行初始化处理(本章主要讲该函数中如何进行初始化
  3. setupRenderEffect:调用设置和渲染有副作用的函数
  // runtime-core/src/renderer
  const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component

    // 1.调用createComponentInstance创建组件实例(创建出来的实例,实例中的属性都是null)
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
        ...
      // 2.对组件实例的props/slot/data等进行初始化处理
      // 对所有数据进行操作和赋值的代码
      setupComponent(instance)
      ...
    // 3.调用设置和渲染有副作用的函数
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )

    if (__DEV__) {
      popWarningContext()
      endMeasure(instance, `mount`)
    }
  }

setupComponent:对props和slots进行初始化,并且在setupStatefulComponent中设置有状态组件

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  // 判断是否是一个有状态的组件
  const isStateful = isStatefulComponent(instance)
  // 初始化props
  initProps(instance, props, isStateful, isSSR)
  // 初始化slots
  initSlots(instance, children)

  // 如果组件有状态,执行状态初始化过程,并返回setup选项的返回值
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}
问题一:为什么对props解构后,就变成非响应式的?

这个问题的答案就在initProps函数中

export function initProps(
  instance: ComponentInternalInstance,
  rawProps: Data | null,
  isStateful: number, // result of bitwise flag comparison
  isSSR = false
) {
  const props: Data = {}
  const attrs: Data = {}
  def(attrs, InternalObjectKey, 1)

  instance.propsDefaults = Object.create(null)

  setFullProps(instance, rawProps, props, attrs)

  ...

  if (isStateful) {
    // stateful
    instance.props = isSSR ? props : shallowReactive(props) // props做了浅层响应式
  } else {
    ...
  }
  instance.attrs = attrs
}

在initProps函数中,会对props做一个浅层响应式,所以对props解构出的值是非响应式的。

setupStatefulComponent:处理setup,将props和setupContext传给setup函数,最后拿到setup的返回值setupResult,传递到handleSetupResult

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

  instance.accessCache = Object.create(null)
  // 创建公共实例/渲染函数代理(通过PublicInstanceProxyHandlers对instance.ctx进行拦截)
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  
  /* setup(props, {attrs, slots, emit, expose}) {
      return {
           title: "标题"
        }
    }
  */ 
  // 1.取出setup
  const { setup } = Component
  if (setup) {
     // 2.取出setupContext,{attrs, slots, emit, expose}
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
    // 在执行setup前,设置当前组件实例对象,所以在setup中可以通过getCurrentInstance获取实例
    setCurrentInstance(instance)
    pauseTracking()
    // 3. 执行setup函数,并且将参数props和{attrs, slots, emit, expose}传递给setup
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()
    
    // 对返回值setupResult是promise的处理
    if (isPromise(setupResult)) {
      setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
        ...
    } else {
      // 4.对返回值setupResult进行处理
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}
问题二:传入setup参数中,分别是props和ctx,它们是如何传递进来的?

setupStatefulComponent函数中的注释可以看出结论,取出props和setupContext,在callWithErrorHandling中回调setup函数,并且将props和setupContext作为参数传递给setup

问题三:setup的执行时刻?为什么setup中没有created钩子?

可以看出setup函数执行的时候(在callWithErrorHandling中执行),组件实例instance已经创建了,所以setup中处理beforeCreate和created是没有意义的。

问题四:如果setup函数的返回值和data这些数据发生冲突,vue3会如何处理?

本问题的答案在PublicInstanceProxyHandlers处理器中

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  get({ _: instance }: ComponentRenderContext, key: string) {
    // 取出ctx,setupState,data等等
    const { ctx, setupState, data, props, accessCache, type, appContext } =
      instance


    if (__DEV__ && key === '__isVue') {
      return true
    }

    let normalizedProps
    if (key[0] !== '$') {
      const n = accessCache![key]
      if (n !== undefined) {
        switch (n) {
          case AccessTypes.SETUP: // 首先从setup中获取
            return setupState[key]
          case AccessTypes.DATA:// 其次从data中获取
            return data[key]
          case AccessTypes.CONTEXT:// 再其次ctx中获取
            return ctx[key]
          case AccessTypes.PROPS: // 最后props中和获取
            return props![key]
          // default: just fallthrough
        }
      } else if (hasSetupBinding(setupState, key)) {
        accessCache![key] = AccessTypes.SETUP
        return setupState[key]
      } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
        accessCache![key] = AccessTypes.DATA
        return data[key]
      } else if (
        ...
}

可以看出setup和data共存下,setup优先级会更高。处理方式是对组件实例上下文instance.ctx做代理,在publicinstanceProxyHandlers的get中会做逻辑判断处理。

继续向后执行

handleSetupResult:对setup返回值进行处理

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {

  // 如果是函数,那么就作为render函数处理
  if (isFunction(setupResult)) {
    
    if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {

      instance.ssrRender = setupResult
    } else {
      // setupResult将处理成render
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) {
    if (__DEV__ && isVNode(setupResult)) {
      warn(
        `setup() should not return VNodes directly - ` +
          `return a render function instead.`
      )
    }

    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      instance.devtoolsRawSetupState = setupResult
    }
    // 将来的渲染函数中会首先从setupState中获取值
    instance.setupState = proxyRefs(setupResult)
    if (__DEV__) {
      exposeSetupStateOnRenderContext(instance)
    }
  } else if (__DEV__ && setupResult !== undefined) {
    warn(
      `setup() should return an object. Received: ${
        setupResult === null ? 'null' : typeof setupResult
      }`
    )
  }
  // 最后都会执行finishComponentSetup函数
  // 里面主要是对options api做兼容
  finishComponentSetup(instance, isSSR)
}
export function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean,
  skipOptions?: boolean
) {
  const Component = instance.type as ComponentOptions
  ...
  if (!instance.render) {
    if (!isSSR && compile && !Component.render) {
        ...
        // 将template模板编译为render函数
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }

    instance.render = (Component.render || NOOP) as InternalRenderFunction

    if (installWithProxy) {
      installWithProxy(instance)
    }
  }

  // support for 2.x options
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
    setCurrentInstance(instance)
    pauseTracking()
    applyOptions(instance) // 兼容vue2的options api
    resetTracking()
    unsetCurrentInstance()
  }

  ...