Vue3.0源码学习——Composition API

469 阅读2分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

Vue3与Vue2在使用上最大的区别就是引入了 Composition API

Composition API 体验

<body>
  <div id="app">
    <h1>vue3 composition API</h1>
    <p>{{ counter }}</p>
  </div>
  <script>
    const app = Vue.createApp({
      setup(props, { emit, slots, attrs }) {
        const counter = Vue.ref(0)
        Vue.onMounted(() => {
          setInterval(() => {
            counter.value++
          }, 1000)
        })
        return {
          counter
        }
      }
    })

    app.mount('#app')
  </script>
</body>

带着疑问看源码

  1. Componsition API(setup) 何时执行?
  2. setup 中为什么不能使用 created 生命周期?
  3. 传入setup参数中,分别是props和ctx,他们从何而来,又是什么?
  • 可以一步一步探究,首先从挂载开始看,在之前的这篇文章 Vue3.0源码学习——初始化流程分析(3.patch过程),了解了挂载过程中会执行渲染函数 render,并会经历 patch 的流程,第一次挂载会走 processComponent => mountComponent,位置 packages\runtime-core\src\component.ts

图片.png

  • mountComponent 中会初始化组件(根组件)实例 setupComponent(instance)

这里组件的实例instance已经创建,因此在setup中使用beforeCreate和created没有意义

  const mountComponent: MountComponentFn = (
    initialVNode, // 根组件vnode
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 2.x compat may pre-create the component instance before actually
    // 兼容vue2.x
    // mounting
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    
    // 1.创建一个组件实例,准确的说是根组件的实例
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))

    ...

    // resolve props and slots for setup context
    // 处理setup上下文
    if (!(__COMPAT__ && compatMountInstance)) {
      if (__DEV__) {
        startMeasure(instance, `init`)
      }
      // 初始化组件实例,等效于vue2源码中 _init()
      setupComponent(instance)
      if (__DEV__) {
        endMeasure(instance, `init`)
      }
    }

    ...
    
  }
  • setupComponent 会返回一个 setupResult = setupStatefulComponent的返回值, 就是用户配置 setup 函数中 return 的值
export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  // 是否有状态组件,非函数式组件
  const isStateful = isStatefulComponent(instance)
  // 初始化props和slots
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  // 如果组件有状态,执行状态初始化过程,并返回setup选项返回值
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

图片.png

  • setupStatefulComponent 中拿到组件的 setup 配置

通过 callWithErrorHandling 执行 setup 并将实例上的 props 和上下文 context 传入

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

  ... 
  // 2. call setup()
  const { setup } = Component
  // 如果用户设置了setup函数
  if (setup) {
    // 创建setup函数的上下文对象
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
    // 设置当前组件的实例,就可以通过 Vue.getCurrentInstance 拿到实例
    setCurrentInstance(instance)
    // 暂停跟踪,提高性能
    pauseTracking()
    // 通过 callWithErrorHandling 调用setup(), 可以捕获异常
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      // 这里就是setup()的参数 props, ctx上下文
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()

    if (isPromise(setupResult)) {
      ...
    } else {
      // 如果setup()返回的不是一个Promise,则执行结果处理函数
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    ...
  }
}

单步调试中可以看到传入的ctx上下文 图片.png