Skip to content

defineXXX 的本质

实际上 defineXXX 函数,有些是编译宏函数,有些是运行时函数。

如何辨别编译/运行时函数

看他们是否是在任意的 js/ts 中可以定义,还是只能在 vuescript setup 代码块定义的。

另外,编译时函数在产物中是被干掉的,是不可见的。原因在于他们的运行是在 nodejs 部分,被直接覆盖掉。

运行时函数

defineComponent / defineAsyncComponent / defineCustomElement 是运行时函数,可以在任意 .js/.ts 文件中使用:

源代码:https://github.com/vuejs/core/blob/a23fb59e83c8b65b27eaa21964c8baa217ab0573/packages/runtime-core/src/apiDefineComponent.ts#L305

js
import { defineComponent, h } from 'vue'

// 可以在任意 .ts 文件中使用
export default defineComponent({
  props: { msg: String },
  setup(props) {
    return () => h('div', props.msg)
  },
})

编译宏函数

以下函数本质上是编译宏,只能在 <script setup> 中使用,编译后会被替换为实际的运行时代码:

宏函数作用引入版本
defineProps声明组件 props3.0
defineEmits声明组件事件3.0
defineExpose声明组件暴露的属性3.0
defineSlots声明插槽类型(仅类型)3.3
defineOptions声明组件选项(如 name)3.3
defineModel声明双向绑定的 model3.4

源代码参考:

defineModel(Vue 3.4+)

defineModel 是 Vue 3.4 引入的编译宏,极大简化了组件的 v-model 双向绑定。

之前实现 v-model 需要手动声明 prop + emit:

html
<script setup>
  // 之前的写法:需要手动管理 prop 和 emit
  const props = defineProps(['modelValue'])
  const emit = defineEmits(['update:modelValue'])

  function updateValue(e) {
    emit('update:modelValue', e.target.value)
  }
</script>

<template>
  <input :value="props.modelValue" @input="updateValue" />
</template>

使用 defineModel 后,一行搞定:

html
<script setup>
  // Vue 3.4+ 的写法:直接返回一个可读写的 ref
  const modelValue = defineModel()
</script>

<template>
  <input v-model="modelValue" />
</template>

defineModel 返回的是一个 ref,可以直接读写,编译器会自动帮你处理 prop 声明和 emit 触发。

在当前 Vue 3.5.x 的编译结果中,它会展开为 props + emits + useModel 的组合,而不是魔法运行时:

js
import { useModel as _useModel } from 'vue'

export default {
  props: { modelValue: {}, modelModifiers: {} },
  emits: ['update:modelValue'],
  setup(__props) {
    const modelValue = _useModel(__props, 'modelValue')
    return { modelValue }
  },
}

支持多个 model 和类型声明:

html
<script setup lang="ts">
  const firstName = defineModel<string>('firstName', { required: true })
  const lastName = defineModel<string>('lastName', { default: '' })
</script>

defineSlots 示例

defineSlots 用于声明插槽的类型,帮助 TypeScript 推导插槽 props:

html
<script setup lang="ts">
  const slots = defineSlots<{
    default(props: { msg: string }): any
    header(props: { title: string }): any
  }>()
</script>

编译宏的本质

所有编译宏在编译后都会被"擦除",替换为对应的运行时代码。比如 defineProps 会变成组件选项中的 props 字段,defineEmits 会变成 emits 字段。

这就是为什么编译宏不需要 import,也不能在 <script setup> 之外使用——它们只存在于编译阶段。

Released under the CC BY-NC-SA 4.0 License.