Compiler macros (defineProps/defineEmits, etc.)
The Core Role of Compiler Macros in Vue 3
Vue 3's <script setup>
syntactic sugar introduces a series of compiler macros, which are specially processed during the compilation phase. defineProps
and defineEmits
are the two most commonly used macros, providing type-safe props and emits declarations for components. These macros are only available in the <script setup>
context and do not require explicit imports.
Basic Usage of defineProps
defineProps
is used to declare the props a component receives, supporting both runtime declarations and type declarations:
<script setup>
// Runtime declaration
const props = defineProps({
title: String,
likes: Number
})
// Type-based declaration
const props = defineProps<{
title?: string
likes: number
}>()
</script>
The type declaration method requires TypeScript support. When using type declarations, default values must be set via the withDefaults
compiler macro:
interface Props {
title?: string
likes: number
}
const props = withDefaults(defineProps<Props>(), {
title: 'Default Title',
likes: 0
})
Detailed Usage of defineEmits
defineEmits
is used to declare events that a component can emit, also supporting two declaration methods:
<script setup>
// Runtime declaration
const emit = defineEmits(['change', 'update'])
// Type-based declaration
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
The type declaration method provides stricter parameter type checking. Events can be emitted as follows:
function handleClick() {
emit('change', 1) // Correct
emit('update', 'new value') // Correct
emit('change', 'string') // Type error
}
Compile-Time Characteristics of Macros
These compiler macros are completely removed during the compilation phase and do not appear in the final generated code. For example:
const props = defineProps<{ title: string }>()
Is compiled into:
const props = __props[0]
This design ensures these API calls incur no runtime overhead. Additionally, IDEs can leverage these macros to provide better type hints and code completion.
Comparison with Options API
Compared to the props
and emits
options in the Options API, compiler macros offer a more concise syntax:
// 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>
Advanced Type Usage
For complex scenarios, advanced TypeScript features can be combined:
<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>
Integration with Other Macros
defineProps
and defineEmits
can be used alongside other compiler macros:
<script setup>
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()
// Used with defineExpose
defineExpose({
reset: () => { /*...*/ }
})
</script>
Practical Application Example
A complete form component example:
<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>
Type Inference and IDE Support
When using type declarations, the Volar plugin provides complete type inference. For example:
const props = defineProps<{
user: {
name: string
age: number
}
}>()
// In the template, the IDE can auto-suggest user.name and user.age
Limitations and Considerations
- These macros can only be used in
<script setup>
- Type declarations require TypeScript support
- Props or emits cannot be dynamically defined
- Macro arguments must be literals, not variables
// Incorrect example
const propDefinition = { title: String }
const props = defineProps(propDefinition) // Compilation error
Comparison with Other Frameworks
Compared to React's props type checking (PropTypes or TypeScript interfaces), Vue's compiler macros offer tighter template integration:
// React approach
interface Props {
title: string;
}
function Component({ title }: Props) {
return <div>{title}</div>
}
// Vue approach
<script setup lang="ts">
defineProps<{ title: string }>()
</script>
<template>
<div>{{ title }}</div>
</template>
Performance Considerations
Since these macros are processed at compile time, they incur no runtime overhead. Compared to the Options API:
- No runtime props option processing is needed
- Emits validation only occurs in development mode
- The generated code is more concise
Custom Macro Extensions
Although uncommon, custom macros can be added via Vue compiler options. This requires modifying the build configuration:
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
compilerOptions: {
macros: {
defineMyMacro: () => ({ /* transformation logic */ })
}
}
})
]
}
Differences from Composition API Functions
Key distinctions between compiler macros and regular Composition API functions:
Feature | Compiler Macros | Regular Composition API |
---|---|---|
Import Method | Automatically available, no import needed | Must be imported from 'vue' |
Compilation | Fully replaced at compile time | Retained as runtime function calls |
Available Context | Only in <script setup> |
Anywhere |
TypeScript Support | Deep integration | Regular type annotations |
Debugging and Error Handling
When types do not match, TypeScript provides detailed error messages:
const emit = defineEmits<{
(e: 'submit', payload: { id: number }): void
}>()
function handleClick() {
emit('submit', { id: '123' }) // Error: 'string' is not assignable to 'number'
}
In the browser console, if incorrect props types are passed, Vue will issue a warning:
[Vue warn]: Invalid prop: type check failed for prop "count". Expected Number, got String
Migration Strategy from Vue 2
When migrating from Vue 2, options can be gradually replaced:
// 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>
Handling in Unit Tests
When testing components that use compiler macros, note the following:
// Testing a component
import { mount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
test('emits change event', () => {
const wrapper = mount(MyComponent, {
props: {
title: 'Test Title'
}
})
wrapper.vm.emit('change') // Test emit call
expect(wrapper.emitted().change).toBeTruthy()
})
Integration with Other Vue Features
Compiler macros seamlessly integrate with other Vue features:
<script setup>
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)
// Using watch to observe props changes
watch(() => props.initialCount, (newVal) => {
count.value = newVal
})
// Providing/injecting context
provide('count', readonly(count))
</script>
Type Export and Reuse
Props interfaces can be exported for use by other components:
// types.ts
export interface PaginationProps {
current: number
pageSize: number
total: number
}
// Usage in a component
<script setup lang="ts">
import type { PaginationProps } from './types'
const props = defineProps<PaginationProps>()
</script>
Reactive Props Destructuring
If props need to be destructured, use toRefs
to maintain reactivity:
<script setup>
const props = defineProps<{ title: string; count: number }>()
const { title, count } = toRefs(props)
watch(count, (newVal) => {
console.log(`count changed to ${newVal}`)
})
</script>
Dynamic Component Scenarios
When used with dynamic components, compiler macros remain effective:
<script setup>
const props = defineProps<{
component: Component
props?: Record<string, any>
}>()
</script>
<template>
<component :is="component" v-bind="props || {}" />
</template>
JSX/TSX Support
Compiler macros also work with JSX/TSX:
// MyComponent.tsx
export default defineComponent({
props: {
// Traditional approach
},
setup(props) {
// ...
}
})
// Or
const MyComponent = defineComponent(
(props: { title: string }) => {
// ...
}
)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Effect作用域API
下一篇:Vite构建工具