Vue3 源码解析系列 - Setup 使用和源码 (一)

355 阅读3分钟

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

前言

Setup 是 Vue3 中一个全新的写法, 今天我们来看看它的内部是如何实现的。

使用

使用 setup 函数时,它将接收两个参数。

  • props 传入组件的属性,它是响应式的。
  • Context 它是一个普通的对象,暴露了一些方法,attrs、slots、emit、expose。

在 setup 函数中,无法访问 this,也就是说无法访问 data、computed、methods 和 refs。只能访问在 Context 中暴露出来的那些方法。

  • 如果 setup 返回一个对象,那么在 template 中就能够访问这些属性。
  • 如果 setup 返回一个函数,那么这个函数会被当作为渲染函数。
import { h, ref, reactive } from 'vue'

export default {
  setup() {
    const readersNumber = ref(0)
    const book = reactive({ title: 'Vue 3 Guide' })
    // 请注意这里我们需要显式使用 ref 的 value
    return () => h('div', [readersNumber.value, book.title])
  }
}

setup 在选项式 API的生命周期中,相当于 beforeCreate 和 created,所以如果我们需要在beforeCreate 和 created中写逻辑的话,应该直接写在 setup 中。

setupStatefulComponent

组件分为有状态组件和无状态组件,是通过vnode 中的 shapeFlag 属性判断的,我们写的组件大部分是有状态的,只有有状态的组件才会有 setup 函数, 所以 setup 函数会在 setupStatefulComponent 方法中进行处理。它的文件位置在 packages/runtime-core/src/component.ts

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

  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,
      [instance.props, setupContext]
    )
    resetTracking()
    unsetCurrentInstance()

    /** 省略代码 */
  }
}

上面是简化后的代码,只保留 setup 部分。我们一起来看下。

  1. 首先,会从组件 Component 中提取出 setup 函数。
  2. 然后会判断 setup.length 是否大于 1,也就是 setup 函数的形参是否大于 1,如果大于 1 就会调用 createSetupContext 创建 context,并赋值给 instance.setupContext。为什么这里要判断形参数量呢?我们上面讲过 setup 可以接收两个参数,props 和 context,而 context 就是通过 createSetupContext 进行创建的,如果我们都没有形参,说明不需要用到 context,自然就没必要去创建 context,所以我们在写代码的时候,如果没有用到 context,最好避免在形参中引入它,这样能省一点性能。
  3. 然后就进行到了调用阶段,通过在 callWithErrorHandling 中进行调用。传入 setup 方法、instance、错误类型 'setup function' 和 由props和context 组成的参数数组。
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
}

callWithErrorHandling 很简单,直接运行 setup 方法,如果有的化就传入参数 props和context,最后返回结果给 setupResult

小结

这篇我们讲了 setup 的使用和介绍,并了解到了 setup 的处理逻辑,知道了在什么时候进行调用。下一篇我们继续从源码入手解析 setup 的处理。