在 Vue3 中,组件化开发已经成为项目开发的基础能力。当一个组件被拆分出来以后,组件之间就需要进行数据与行为的传递:
- 子组件 → 父组件:使用 Events(组件事件)
很多初学者学习 Vue3 时,往往只关注 Props,却忽略了组件事件的重要性。
实际上,在企业级项目开发中:
几乎都离不开组件事件。
Vue3 对组件事件进行了进一步规范化,引入了 emits 配置,使组件通信更加清晰、安全、可维护。
本文将系统讲解 Vue3 组件事件的所有核心知识点,包括:
一、什么是组件事件
组件事件本质上是一种:
子组件通知父组件的通信机制
例如:
父组件:
<UserForm />
用户点击提交按钮:
<button>提交</button>
此时子组件需要告诉父组件:
用户点击了提交按钮
这时候就需要:
emit()
触发一个组件事件。
二、触发组件事件
Vue 提供:
$emit()
用于触发自定义事件。
例如:
<template>
<button @click="$emit('save')">
保存
</button>
</template>
当按钮点击时:
$emit('save')
会向父组件发送一个事件。
三、父组件监听事件
子组件:
<button @click="$emit('save')">
保存
</button>
父组件:
<UserForm @save="handleSave" />
const handleSave = () => {
console.log("执行保存")
}
执行流程:
点击按钮
↓
emit('save')
↓
父组件监听到 save
↓
执行 handleSave
四、事件名称规范
推荐使用:
kebab-case
例如:
emit("user-login")
父组件:
<UserForm @user-login="login" />
虽然 Vue 支持自动转换:
emit("userLogin")
监听:
<UserForm @user-login="login" />
依然可以正常工作。
但是企业开发统一推荐:
user-login
form-submit
page-change
data-refresh
不要写:
userLogin
formSubmit
这样更符合 HTML 属性命名规范。
五、事件传递参数
实际开发中,事件通常需要携带数据。
例如:
<button @click="$emit('delete', 1001)">
删除
</button>
父组件:
<UserTable @delete="handleDelete" />
const handleDelete = (id) => {
console.log(id)
}
输出:
1001
六、传递多个参数
子组件:
emit(
"submit",
username,
password
)
父组件:
const submit = (
username,
password
) => {
}
接收:
zhangsan
123456
七、传递对象参数
企业项目中最常见。
子组件:
emit("submit", {
username: "admin",
password: "123456"
})
父组件:
const submit = (form) => {
console.log(form)
}
输出:
{
username:"admin",
password:"123456"
}
这种方式扩展性最好。
八、defineEmits
在 Composition API 中:
<script setup>
</script>
无法直接使用:
this.$emit
需要:
defineEmits()
例如:
<script setup>
const emit = defineEmits([
"save"
])
const submit = () => {
emit("save")
}
</script>
这也是 Vue3 官方推荐写法。
九、defineEmits 数组写法
最简单方式:
const emit = defineEmits([
"save",
"delete",
"update"
])
表示组件允许触发:
save
delete
update
十、对象写法
Vue3 推荐大型项目使用对象写法。
const emit = defineEmits({
save:null,
delete:null
})
这样便于扩展事件校验。
十一、事件校验
类似 Props 校验。
例如:
const emit = defineEmits({
submit:(data)=>{
return !!(
data.username &&
data.password
)
}
})
触发:
emit("submit", {
username:"admin",
password:"123456"
})
校验通过。
如果:
emit("submit", {})
控制台警告。
十二、Options API 中的 emits
除了 Composition API。
Options API 也支持:
export default {
emits:[
"save"
]
}
触发:
this.$emit("save")
Vue3 新增了 emits 选项,用于明确声明组件可以发出的事件。
十三、为什么要声明 emits
很多人疑惑:
直接 emit 不行吗?
可以。
但声明后有很多优势:
IDE 自动提示
emit("save")
拥有代码提示。
防止拼写错误
错误:
emit("svae")
声明:
emits:["save"]
Vue 可以发现问题。
组件接口更清晰
类似:
props:[]
定义输入。
emits:[]
定义输出。
形成完整组件契约。
十四、事件只会向父组件传递
很多新手认为:
emit()
类似 DOM 冒泡。
实际上:
不会自动冒泡
组件事件只会:
子组件
↓
父组件
不会:
子组件
↓
父组件
↓
爷组件
如果需要继续传递:
再次 emit
即可。社区讨论中也经常提到这一点,自定义组件事件不会像原生 DOM 事件那样自动向上冒泡。
十五、监听一次事件
Vue 支持:
<UserForm
@save.once="save"
/>
只执行一次。
适用于:
首次加载
首次授权
首次确认
场景。
十六、v-model 本质就是事件
很多人认为:
<UserInput
v-model="name"
/>
是特殊语法。
实际上它最终会转换成:
<UserInput
:modelValue="name"
@update:modelValue="
name = $event
"
/>
也就是说:
v-model
=
Prop
+
事件
这是 Vue3 组件双向绑定的核心机制。
十七、自定义 v-model
子组件:
<script setup>
const props =
defineProps([
"modelValue"
])
const emit =
defineEmits([
"update:modelValue"
])
</script>
<input
:value="modelValue"
@input="
emit(
'update:modelValue',
$event.target.value
)
"
/>
父组件:
<MyInput
v-model="username"
/>
即可实现双向绑定。
十八、Vue3.4 的 defineModel
Vue3.4 开始新增:
defineModel()
例如:
const model =
defineModel()
直接获得:
modelValue
+
update:modelValue
能力。
代码大幅简化。
十九、多个 v-model
Vue3 支持:
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
对应:
defineModel("firstName")
defineModel("lastName")
一个组件拥有多个双向绑定能力。
二十、企业级项目常见事件设计
表格组件
emit("page-change")
emit("sort-change")
emit("selection-change")
搜索组件
emit("search")
emit("reset")
弹窗组件
emit("confirm")
emit("cancel")
上传组件
emit("success")
emit("error")
表单组件
emit("submit")
emit("validate")
二十一、企业级开发规范
必须声明 emits
推荐:
defineEmits([
"save",
"delete"
])
不要直接:
emit("save")
而不声明。
事件名统一使用 kebab-case
推荐:
page-change
row-click
form-submit
不要:
pageChange
rowClick
参数统一对象化
推荐:
emit("save", {
id:1,
name:"Tom"
})
不要:
emit(
"save",
1,
"Tom",
true
)
后期维护困难。
不要滥用事件链
错误:
A
↓
B
↓
C
↓
D
层层 emit。
复杂项目建议:
Pinia
Provide/Inject
状态管理
统一处理共享状态。
总结
Vue3 的组件事件是组件通信体系中的核心组成部分。
其核心思想非常简单:
Props 负责输入
Events 负责输出
开发流程通常为:
父组件传递 Props
↓
子组件接收数据
↓
用户触发操作
↓
emit 发送事件
↓
父组件监听事件
↓
更新状态
而 Vue3 又进一步通过:
defineEmits
emits
事件校验
v-model
defineModel
构建出完整且规范的组件通信机制。
掌握组件事件之后,再结合:
Props
Slots
Provide/Inject
Pinia