Vue中defineProps和defineEmits的使用小结
一句话总结:defineProps 声明组件接收哪些 props,defineEmits 声明组件能触发哪些事件,它们是 <script setup> 的编译时宏,不需要 import,但必须搭配运行时校验和类型标注才能用得稳!
一、defineProps 和 defineEmits 是什么?
问题背景:Vue3 <script setup> 语法糖中,组件的 props 和 emits 不再通过 props/emits 选项声明,而是用 defineProps 和 defineEmits 宏来声明。
| 特性 | Options API | script setup 宏 |
|---|---|---|
| 声明 props | props: { ... } | defineProps({ ... }) |
| 声明 emits | emits: [...] | defineEmits([...]) |
| 需要 import | 不需要 | 不需要(编译宏) |
| 类型标注 | 无 | 支持 TS withDefaults |
| 默认值 | default | withDefaults 或运行时 default |
<!-- ❌ 错误:在 script setup 中手动 import defineProps -->
<script setup>
import { defineProps } from 'vue' // ❌ 不需要也不能 import!
defineProps({ msg: String })
</script>
二、5 大高频场景 & 代码示例
场景 1:基本类型校验 → 运行时声明
<!-- ✅ 正确:运行时声明,带类型和默认值 -->
<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
},
items: {
type: Array,
default: () => [] // 对象/数组默认值必须用工厂函数
},
callback: {
type: Function,
default: () => () => {}
}
})
</script>
<template>
<h1>{{ title }}</h1>
<p>共 {{ count }} 项</p>
</template>
<!-- ❌ 错误:对象默认值直接写对象 -->
<script setup>
const props = defineProps({
config: {
type: Object,
default: { theme: 'dark' } // ❌ 每个实例共享同一引用!
}
})
</script>
场景 2:TypeScript 类型标注 → 泛型声明
<!-- ✅ 正确:TS 泛型声明,类型推断完整 -->
<script setup lang="ts">
interface Props {
title: string
count?: number // 可选
items?: string[]
}
const props = defineProps<Props>()
// withDefaults 提供默认值
const props2 = withDefaults(defineProps<Props>(), {
count: 0,
items: () => ['默认项']
})
</script>
<!-- ❌ 错误:运行时声明和泛型声明混用 -->
<script setup lang="ts">
// 不能同时用 type 对象和泛型
const props = defineProps<Props & {
extra: {
type: String, // ❌ 泛型里不能写运行时校验
required: true
}
}>()
</script>
场景 3:defineEmits 事件声明 → 带校验
<!-- ✅ 正确:声明事件 + 校验函数 -->
<script setup>
const emit = defineEmits({
// 校验函数
submit: (payload) => {
if (!payload.email.includes('@')) {
console.warn('email 格式不对')
return false
}
return true
},
cancel: null // null 表示不校验
})
function handleSubmit() {
emit('submit', { email: 'boss@example.com', name: '老板' })
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<button type="submit">提交</button>
<button @click="emit('cancel')">取消</button>
</form>
</template>
<!-- ❌ 错误:没声明 emit,直接用 $emit(setup 中没有 $emit) -->
<script setup>
function handleClick() {
$emit('submit', data) // ❌ ReferenceError: $emit is not defined
}
</script>
<!-- ✅ 正确:TS 泛型声明 emits -->
<script setup lang="ts">
const emit = defineEmits<{
(e: 'submit', payload: { email: string; name: string }): void
(e: 'cancel'): void
}>()
</script>
场景 4:v-model 双向绑定 → defineProps + defineEmits 配合
<!-- ✅ 正确:自定义组件实现 v-model -->
<!-- MyInput.vue -->
<script setup>
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
<!-- 父组件使用 --> <template> <MyInput v-model="text" /> <!-- 等价于 --> <MyInput :model-value="text" @update:model-value="text = $event" /> </template>
<!-- ✅ 进阶:多个 v-model -->
<script setup>
const props = defineProps<{
modelValue: string
title: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', v: string): void
(e: 'update:title', v: string): void
}>()
</script>
<!-- 父组件 -->
<template>
<MyInput v-model="text" v-model:title="titleText" />
</template>
场景 5:defineExpose → 暴露方法给父组件 ref
<!-- ✅ 正确:defineExpose 暴露指定方法,不暴露内部实现 -->
<!-- ChildForm.vue -->
<script setup>
import { ref } from 'vue'
const formData = ref({ name: '', age: 0 })
// 内部方法,不暴露
function validate() {
return formData.value.name.length > 0
}
// 只暴露需要的方法
defineExpose({
validate,
getData: () => formData.value,
reset: () => { formData.value = { name: '', age: 0 } }
})
</script>
<!-- 父组件 -->
<script setup>
import { ref, onMounted } from 'vue'
import ChildForm from './ChildForm.vue'
const formRef = ref()
function handleSubmit() {
if (formRef.value?.validate()) {
const data = formRef.value.getData()
console.log(data)
}
}
</script>
<template>
<ChildForm ref="formRef" />
<button @click="handleSubmit">提交</button>
</template>
<!-- ❌ 错误:没 defineExpose,父组件 ref 拿不到任何东西 -->
<script setup>
function validate() { return true }
// 忘了 defineExpose
// 父组件 formRef.value.validate → undefined
</script>
三、万能兜底:Props/Emits 声明模板
| 场景 | 推荐写法 |
|---|---|
| 简单项目 | 运行时声明 defineProps({ x: String }) |
| TS 项目 | 泛型声明 defineProps<Props>() + withDefaults |
| 事件校验 | defineEmits({ submit: (p) => true }) |
| v-model | modelValue prop + update:modelValue emit |
| 多 v-model | 命名 v-model v-model:title |
| 暴露方法 | defineExpose({ method }) |
万能模板:
<script setup lang="ts">
import { computed } from 'vue'
// 1. Props 泛型 + 默认值
interface Props {
modelValue: string
title?: string
items?: string[]
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
title: '默认标题',
items: () => [],
disabled: false
})
// 2. Emits 泛型声明
const emit = defineEmits<{
(e: 'update:modelValue', v: string): void
(e: 'submit', payload: { value: string }): void
(e: 'cancel'): void
}>()
// 3. computed 代理 v-model
const innerValue = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v)
})
// 4. defineExpose 暴露方法
defineExpose({
focus: () => {},
reset: () => {}
})
</script>
四、预防 checklist(不再踩坑)
- 不要 import defineProps / defineEmits,它们是编译宏
- 对象/数组类型的 default 用工厂函数 () => ({}) / () => []
- <script setup> 中没有 this,不能用 this.$emit,必须用 defineEmits 返回的 emit
- 声明了 emits 的事件不会出现在 $attrs 上(不会被当原生事件)
- v-model 的 prop 名叫 modelValue,事件名叫 update:modelValue
- 多个 v-model 用具名 model v-model:title="titleText"
- TS 项目优先用泛型声明 + withDefaults
- 需要父组件 ref 调用子组件方法时,必须 defineExpose
- emit 带校验函数时,返回 false 会触发警告但不阻止 emit
五、一句话总结
defineProps 声明接收、defineEmits 声明触发、defineExpose 声明暴露——三个编译宏撑起 <script setup> 的组件 API,搭配 TS 泛型和 withDefaults 才是完整形态。
到此这篇关于Vue中defineProps和defineEmits的使用小结的文章就介绍到这了,更多相关Vue defineProps和defineEmits内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
基于Vue2-Calendar改进的日历组件(含中文使用说明)
这篇文章主要介绍了基于Vue2-Calendar改进的日历组件(含中文使用说明)的相关知识,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下2019-04-04
vue3如何使用postcss-px-to-viewport适配屏幕
这篇文章主要介绍了vue3如何使用postcss-px-to-viewport适配屏幕问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2023-03-03
VUE 项目在IE11白屏报错 SCRIPT1002: 语法错误的解决
这篇文章主要介绍了VUE 项目在IE11白屏报错 SCRIPT1002: 语法错误的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-09-09


最新评论