Vue3系列 - setup上下文的创建

2025 年 9 月 28 日 星期日
/
8

Vue3系列 - setup上下文的创建

setup上下文的创建

在Vue3的组合式API中,setup函数接收两个参数:props和上下文(context)。上下文对象提供了与组件交互的方法和属性。让我们深入了解Vue2.7中setup上下文的创建过程。

createSetupContext函数

setup上下文通过createSetupContext函数创建:

function createSetupContext(vm) {
  let exposeCalled = false
  return {
    get attrs() {
      if (!vm._attrsProxy) {
        const proxy = (vm._attrsProxy = {})
        def(proxy, '_v_attr_proxy', true)
        syncSetupProxy(proxy, vm.$attrs, emptyObject, vm, '$attrs')
      }
      return vm._attrsProxy
    },
    get listeners() {
      if (!vm._listenersProxy) {
        const proxy = (vm._listenersProxy = {})
        syncSetupProxy(proxy, vm.$listeners, emptyObject, vm, '$listeners')
      }
      return vm._listenersProxy
    },
    get slots() {
      return initSlotsProxy(vm)
    },
    emit: bind(vm.$emit, vm) as any,
    expose(exposed) {
      // expose API实现...
    }
  }
}

上下文对象包含以下属性和方法:

  1. attrs:非prop的属性
  2. listeners:事件监听器
  3. slots:插槽
  4. emit:触发事件的方法
  5. expose:暴露公共属性的方法

attrs和listeners的代理

attrslisteners是通过代理实现的,它们会在访问时才创建代理对象:

get attrs() {
  if (!vm._attrsProxy) {
    const proxy = (vm._attrsProxy = {})
    def(proxy, '_v_attr_proxy', true)
    syncSetupProxy(proxy, vm.$attrs, emptyObject, vm, '$attrs')
  }
  return vm._attrsProxy
}

syncSetupProxy函数负责同步代理对象和原始对象:

export function syncSetupProxy(
  to: any,
  from: any,
  prev: any,
  instance: Component,
  type: string
) {
  let changed = false
  for (const key in from) {
    if (!(key in to)) {
      changed = true
      defineProxyAttr(to, key, instance, type)
    } else if (from[key] !== prev[key]) {
      changed = true
    }
  }
  for (const key in to) {
    if (!(key in from)) {
      changed = true
      delete to[key]
    }
  }
  return changed
}

slots的代理

slots也是通过代理实现的:

function initSlotsProxy(vm) {
  if (!vm._slotsProxy) {
    syncSetupSlots((vm._slotsProxy = {}), vm.$scopedSlots)
  }
  return vm._slotsProxy
}

export function syncSetupSlots(to, from) {
  for (const key in from) {
    to[key] = from[key]
  }
  for (const key in to) {
    if (!(key in from)) {
      delete to[key]
    }
  }
}

emit方法

emit方法是组件实例的$emit方法的绑定版本:

emit: bind(vm.$emit, vm)

这使得在setup函数中可以直接使用emit触发事件,而不需要使用this.$emit

expose方法

expose方法允许组件有选择地暴露公共属性:

expose(exposed) {
  if (__DEV__) {
    if (exposeCalled) {
      warn(`expose() should be called only once per setup().`, vm)
    }
    exposeCalled = true
  }
  if (exposed) {
    Object.keys(exposed).forEach(key =>
      proxyWithRefUnwrap(vm, exposed, key)
    )
  }
}

这在使用<script setup>时特别有用,因为默认情况下<script setup>中的变量不会暴露给模板之外。

辅助函数

Vue2.7还提供了一些辅助函数,用于在setup函数中访问上下文的各个部分:

export function useSlots() {
  return getContext().slots
}

export function useAttrs() {
  return getContext().attrs
}

export function useListeners() {
  return getContext().listeners
}

总结

setup上下文提供了与组件交互的方法和属性,使组合式API能够完全替代选项式API的功能。理解setup上下文的创建过程,有助于我们更好地使用组合式API,编写更加模块化、可维护的Vue组件。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...