编译器宏(defineProps/defineEmits等)
编译器宏在Vue 3中的核心作用
Vue 3的<script setup>
语法糖引入了一系列编译器宏,这些宏在编译阶段会被特殊处理。defineProps
和defineEmits
是最常用的两个宏,它们为组件提供了类型安全的props和emits声明方式。这些宏只在<script setup>
上下文中可用,不需要显式导入。
defineProps的基本用法
defineProps
用于声明组件接收的props,支持运行时声明和类型声明两种方式:
<script setup>
// 运行时声明
const props = defineProps({
title: String,
likes: Number
})
// 基于类型的声明
const props = defineProps<{
title?: string
likes: number
}>()
</script>
类型声明方式需要TypeScript支持。当使用类型声明时,默认值需要通过withDefaults
编译器宏来设置:
interface Props {
title?: string
likes: number
}
const props = withDefaults(defineProps<Props>(), {
title: '默认标题',
likes: 0
})
defineEmits的详细用法
defineEmits
用于声明组件可以触发的事件,同样支持两种声明方式:
<script setup>
// 运行时声明
const emit = defineEmits(['change', 'update'])
// 基于类型的声明
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
类型声明方式提供了更严格的参数类型检查。使用时可以这样触发事件:
function handleClick() {
emit('change', 1) // 正确
emit('update', 'new value') // 正确
emit('change', 'string') // 类型错误
}
宏的编译时特性
这些编译器宏在编译阶段会被完全移除,不会出现在最终生成的代码中。例如:
const props = defineProps<{ title: string }>()
会被编译为:
const props = __props[0]
这种设计使得这些API调用不会产生运行时开销。同时,IDE可以利用这些宏提供更好的类型提示和代码补全。
与Options API的对比
相比Options API中的props
和emits
选项,编译器宏提供了更简洁的语法:
// Options API
export default {
props: {
title: String
},
emits: ['change'],
setup(props, { emit }) {
// ...
}
}
// Composition API with <script setup>
<script setup>
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ (e: 'change'): void }>()
</script>
高级类型用法
对于复杂场景,可以结合TypeScript的高级类型特性:
<script setup lang="ts">
interface User {
id: number
name: string
}
const props = defineProps<{
userList: User[]
callback: (user: User) => boolean
}>()
const emit = defineEmits<{
loaded: [users: User[]]
error: [message: string, code?: number]
}>()
</script>
与其他宏的配合使用
defineProps
和defineEmits
可以与其他编译器宏一起使用:
<script setup>
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()
// 与defineExpose配合
defineExpose({
reset: () => { /*...*/ }
})
</script>
实际应用场景示例
一个完整的表单组件示例:
<script setup lang="ts">
interface FormItem {
field: string
value: any
required?: boolean
}
const props = defineProps<{
items: FormItem[]
submitText?: string
}>()
const emit = defineEmits<{
(e: 'submit', formData: Record<string, any>): void
(e: 'cancel'): void
}>()
const formData = computed(() =>
props.items.reduce((obj, item) => {
obj[item.field] = item.value
return obj
}, {} as Record<string, any>)
)
function handleSubmit() {
if (validateForm()) {
emit('submit', formData.value)
}
}
</script>
类型推断与IDE支持
使用类型声明方式时,Volar插件能提供完整的类型推断。例如:
const props = defineProps<{
user: {
name: string
age: number
}
}>()
// 在模板中使用时,IDE能自动提示user.name和user.age
限制与注意事项
- 这些宏只能在
<script setup>
中使用 - 类型声明需要TypeScript支持
- 不能动态定义props或emits
- 宏的参数必须是字面量,不能是变量
// 错误示例
const propDefinition = { title: String }
const props = defineProps(propDefinition) // 编译错误
与其他框架的对比
相比React的props类型检查(PropTypes或TypeScript接口),Vue的编译器宏提供了更紧密的模板集成:
// React方式
interface Props {
title: string;
}
function Component({ title }: Props) {
return <div>{title}</div>
}
// Vue方式
<script setup lang="ts">
defineProps<{ title: string }>()
</script>
<template>
<div>{{ title }}</div>
</template>
性能考量
由于这些宏在编译时处理,不会产生运行时开销。与Options API相比:
- 不需要运行时props选项处理
- emits验证只在开发模式下进行
- 生成的代码更加精简
自定义宏扩展
虽然不常见,但可以通过Vue编译器选项添加自定义宏。这需要修改构建配置:
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
compilerOptions: {
macros: {
defineMyMacro: () => ({ /* 转换逻辑 */ })
}
}
})
]
}
与Composition API函数的区别
编译器宏与常规Composition API函数的关键区别:
特性 | 编译器宏 | 常规Composition API |
---|---|---|
导入方式 | 自动可用,无需导入 | 需要从'vue'导入 |
编译处理 | 编译时完全替换 | 保留为运行时函数调用 |
可用上下文 | 仅<script setup> |
任何地方 |
TypeScript支持 | 深度集成 | 常规类型注解 |
调试与错误处理
当类型不匹配时,TypeScript会提供详细的错误信息:
const emit = defineEmits<{
(e: 'submit', payload: { id: number }): void
}>()
function handleClick() {
emit('submit', { id: '123' }) // 错误: 'string'不能赋值给'number'
}
在浏览器控制台中,如果传递了错误的props类型,Vue会发出警告:
[Vue warn]: Invalid prop: type check failed for prop "count". Expected Number, got String
与Vue 2的迁移策略
从Vue 2迁移时,可以逐步替换options:
// Vue 2
export default {
props: {
title: String
},
emits: ['change'],
methods: {
update() {
this.$emit('change')
}
}
}
// Vue 3
<script setup>
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ (e: 'change'): void }>()
function update() {
emit('change')
}
</script>
单元测试中的处理
测试使用编译器宏的组件时,需要注意:
// 测试组件
import { mount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
test('emits change event', () => {
const wrapper = mount(MyComponent, {
props: {
title: '测试标题'
}
})
wrapper.vm.emit('change') // 测试emit调用
expect(wrapper.emitted().change).toBeTruthy()
})
与其他Vue特性集成
编译器宏可以无缝与其他Vue特性配合:
<script setup>
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)
// 使用watch观察props变化
watch(() => props.initialCount, (newVal) => {
count.value = newVal
})
// 提供/注入上下文
provide('count', readonly(count))
</script>
类型导出与复用
可以导出props接口供其他组件使用:
// types.ts
export interface PaginationProps {
current: number
pageSize: number
total: number
}
// 组件中使用
<script setup lang="ts">
import type { PaginationProps } from './types'
const props = defineProps<PaginationProps>()
</script>
响应式props解构
如果需要解构props,应使用toRefs
保持响应性:
<script setup>
const props = defineProps<{ title: string; count: number }>()
const { title, count } = toRefs(props)
watch(count, (newVal) => {
console.log(`count changed to ${newVal}`)
})
</script>
动态组件场景
与动态组件一起使用时,编译器宏仍然有效:
<script setup>
const props = defineProps<{
component: Component
props?: Record<string, any>
}>()
</script>
<template>
<component :is="component" v-bind="props || {}" />
</template>
JSX/TSX支持
在使用JSX/TSX时,编译器宏同样适用:
// MyComponent.tsx
export default defineComponent({
props: {
// 传统方式
},
setup(props) {
// ...
}
})
// 或者
const MyComponent = defineComponent(
(props: { title: string }) => {
// ...
}
)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Effect作用域API
下一篇:Vite构建工具