Vue2里的events到底怎么玩?从基础到实战一次讲透
刚接触Vue2的小伙伴,是不是一听到“events”就脑袋发懵?自定义事件是干啥的?怎么在组件里传消息?和浏览器的点击、输入这些事件有啥不一样?别担心,这篇文章把Vue2 events从“是啥”到“怎么用”,再到“实战避坑”全讲明白,哪怕你是刚入门的新手,看完也能上手写组件通信~
Vue2里的events是啥?和DOM事件有啥区别?
先搞清楚核心概念:Vue2的events主要指自定义事件,作用是组件之间传递消息(尤其是子组件给父组件传数据),那它和我们熟悉的DOM事件(比如按钮点击@click、输入框输入@input)有啥不一样?
举个例子:浏览器里的按钮点击(@click)是原生DOM事件,触发者是浏览器(用户点了按钮),作用在DOM元素上,而Vue的自定义事件,触发者是我们写的组件(比如子组件里用this.$emit('xxx')触发),作用是让组件之间能“对话”。
简单说:DOM事件是“浏览器和元素”的互动,自定义事件是“组件和组件”的互动。
那自定义事件咋工作的?核心逻辑是“子传父”:子组件通过$emit触发一个自定义事件,把数据“抛”出去;父组件通过@事件名监听这个事件,拿到子组件传来的数据,再做处理。
怎么在组件里定义和触发自定义事件?
分两步走:子组件触发事件 + 父组件监听事件,咱们用一个“子组件点击按钮,给父组件传消息”的例子来理解。
步骤1:子组件里用$emit触发事件
假设子组件叫Child.vue,里面有个按钮,点击后要把“你好,父组件!”传给父组件,代码这样写:
<template>
<button @click="sendMsg">点我给父组件发消息</button>
</template>
<script>
export default {
methods: {
sendMsg() {
// $emit(事件名, 要传的数据)
this.$emit('send-message', '你好,父组件!');
}
}
}
</script>
这里的关键是this.$emit('事件名', 数据) —— 事件名自己起(比如send-message),数据可以是字符串、对象、数组啥的。
步骤2:父组件里用@事件名监听
父组件叫Parent.vue,要接收子组件的消息,得这么写:
<template>
<div>
<!-- 引入子组件,用@事件名绑定回调函数 -->
<Child @send-message="handleMsg" />
<p>子组件传来的消息:{{ msg }}</p>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
msg: ''
}
},
methods: {
handleMsg(data) {
// data就是子组件$emit传过来的数据
this.msg = data;
}
}
}
</script>
父组件里,<Child @send-message="handleMsg" /> 表示“监听子组件的send-message事件,触发时执行handleMsg函数”。handleMsg的参数data,就是子组件传过来的“你好,父组件!”。
这样一来,子组件点按钮,父组件就能收到消息并更新页面,组件通信就完成啦~
events能传多个参数吗?怎么处理?
当然能!子组件$emit的时候,可以传多个参数;父组件的回调函数也能对应接收,举个例子:子组件要传“姓名”和“年龄”两个数据。
子组件传多个参数
修改Child.vue的sendMsg方法:
methods: {
sendMsg() {
const name = '小明';
const age = 18;
// 传两个参数:name和age
this.$emit('send-info', name, age);
}
}
父组件接收多个参数
父组件的handleMsg函数要对应接收两个参数:
methods: {
handleMsg(name, age) {
console.log('姓名:', name, '年龄:', age); // 输出:姓名:小明 年龄:18
}
}
注意哦:父组件的回调函数参数顺序,要和子组件$emit传参的顺序一致~ 要是传了三个参数,父组件回调也得写三个参数来接收。
事件修饰符在events里咋用?和DOM事件有啥不同?
Vue里的事件修饰符(比如.stop、.prevent、.once这些),在自定义事件和DOM事件里的表现不一样,得仔细区分。
自定义事件能用哪些修饰符?
自定义事件(子传父的那种)不是DOM事件,所以像.stop(阻止冒泡)、.prevent(阻止默认行为)这些对自定义事件无效,但有个修饰符很常用:.once —— 让事件只触发一次。
比如父组件监听事件时加.once:
<Child @send-message.once="handleMsg" />
这样不管子组件点多少次按钮,handleMsg只会执行第一次,后面再点就没反应了。
.native修饰符是干啥的?
有时候你会看到<Child @click.native="xxx">这种写法,这是把自定义组件的根元素当成DOM元素,绑定原生DOM事件,比如Child组件的根元素是button,那@click.native就是给这个button绑定原生点击事件,但这和自定义事件没关系,属于“给组件绑原生DOM事件”的技巧~
父组件给子组件传事件?还是子传父?别搞混!
很多新手容易把“父传子”和“子传父”搞混,这里明确一下:
- 父传子:用
props传数据(比如父组件给子组件传标题、列表数据)。 - 子传父:用自定义事件(events)传消息(比如子组件点击后,告诉父组件“我要修改数据啦”)。
那父组件能给子组件传“事件”吗?比如把父组件的方法通过props传给子组件,让子组件调用?理论上能,但不推荐!因为Vue提倡“单向数据流”(父→子通过props传数据,子→父通过events通知),如果子组件直接调用父组件的方法,容易让数据流向变乱,后期维护麻烦,所以优先用“子组件$emit事件,父组件监听处理”的方式~
多层组件嵌套时,events咋跨层级通信?
比如有个爷爷组件(Grandpa)→ 爸爸组件(Father)→ 儿子组件(Son),现在Son要给Grandpa传消息,总不能让Son→Father→Grandpa层层$emit吧?太麻烦!这时候可以用事件总线(Event Bus)或者Vuex。
方法1:事件总线(Event Bus)
原理是创建一个空的Vue实例,当“中间件”,所有组件都通过这个实例来$emit和$on事件,实现跨层级通信。
步骤1:创建事件总线
在main.js(或单独的bus.js)里创建:
import Vue from 'vue' // 创建空Vue实例,作为事件总线 export const bus = new Vue()
步骤2:子组件(Son)触发事件
Son.vue里,导入bus,用bus.$emit发消息:
<template>
<button @click="sendToGrandpa">给爷爷发消息</button>
</template>
<script>
import { bus } from '../main.js' // 假设bus在main.js里
export default {
methods: {
sendToGrandpa() {
bus.$emit('grandpa-msg', '爷爷好!我是孙子~');
}
}
}
</script>
步骤3:爷爷组件(Grandpa)监听事件
Grandpa.vue里,导入bus,用bus.$on监听:
<template>
<div>
<p>孙子传来的消息:{{ msg }}</p>
</div>
</template>
<script>
import { bus } from '../main.js'
export default {
data() {
return {
msg: ''
}
},
created() {
// 监听事件
bus.$on('grandpa-msg', (data) => {
this.msg = data;
})
},
beforeDestroy() {
// 销毁事件,防止内存泄漏
bus.$off('grandpa-msg');
}
}
</script>
这样,不管多少层嵌套,只要通过bus.$emit和bus.$on,就能跨组件通信~ 但要注意在组件销毁前用$off销毁事件,否则重复创建组件时会多次触发,导致 bug。
方法2:Vuex(更适合复杂场景)
如果项目里已经用了Vuex,跨组件通信更推荐用Vuex的state和mutation,比如Son组件提交mutation,Grandpa组件通过计算属性获取state里的数据,不过Vuex更适合“多组件共享数据”的场景,简单跨层级用事件总线更轻便~
自定义事件和生命周期有啥关系?啥时候绑定/销毁?
如果是用模板里@事件名的方式(比如父组件<Child @xxx="xxx" />),Vue会自动帮我们处理事件的绑定和销毁,不用操心,但如果是用事件总线或者在JS里手动$on,就得结合生命周期钩子来管理。
比如用事件总线时:
created钩子:组件创建后,用bus.$on监听事件(因为created时组件已经能访问this,且DOM还没渲染,适合绑定事件)。beforeDestroy钩子:组件销毁前,用bus.$off销毁事件(否则事件还在,下次创建组件时会重复监听,导致多次触发)。
看个例子(爷爷组件用事件总线时的生命周期处理):
export default {
data() { ... },
created() {
// 绑定事件
bus.$on('grandpa-msg', this.handleMsg);
},
methods: {
handleMsg(data) {
this.msg = data;
}
},
beforeDestroy() {
// 销毁事件
bus.$off('grandpa-msg', this.handleMsg);
}
}
这样能保证事件在组件可用时监听,销毁时清除,避免内存泄漏和重复触发~
实战中常见的events错误,怎么避坑?
新手用自定义事件时,很容易踩这些坑,提前避坑少熬夜!
坑1:事件名大小写不统一
Vue2里,自定义事件名推荐用全小写或kebab-case(短横线分隔),因为HTML属性不区分大小写,模板里的@事件名是全小写的。
比如子组件$emit('sendMessage')(驼峰),父组件模板里写@send-message才能监听到(因为HTML里@sendMessage会被当成@sendmessage,和子组件的sendMessage不匹配),所以事件名统一用全小写或kebab-case,比如send-message、sendmsg,避免大小写坑!
坑2:父组件忘记绑定事件,子组件$emit没反应
子组件都$emit了,父组件页面没变化?先检查父组件有没有写@事件名,比如子组件$emit('send-msg'),父组件得写<Child @send-msg="xxx" />,没写的话肯定收不到~
坑3:参数传递/接收错误
子组件传了多个参数,父组件回调只写了一个参数,结果拿到的是第一个参数,后面的丢了,比如子组件$emit('send', a, b),父组件handle(data) { ... },这时候data是a,b没收到,所以传参和接收的参数数量、顺序要一致!
坑4:事件总线没销毁,重复触发
用事件总线时,组件销毁前没调用bus.$off,下次再创建组件时,旧的事件还在,导致一次操作触发多次回调,解决方法:在beforeDestroy钩子用$off销毁事件。
Vue2的events和Vue3有啥区别?学了Vue2对Vue3有帮助吗?
Vue3对自定义事件做了优化,核心逻辑还是“子传父用emit”,但有这些变化:
- Vue3需要在组件的
emits选项里显式声明自定义事件(比如emits: ['send-msg']),让代码更清晰,还能做类型校验。 - Vue3的
$emit是从setup里的上下文获取(ctx.emit),而不是this.$emit。
但核心思想没变:子组件触发事件,父组件监听,所以学懂Vue2的events,理解组件通信的逻辑,转到Vue3只是语法细节调整,上手会很快~
实战案例:用events做一个“点赞组件”
光说不练假把式,咱们做个小案例:子组件是点赞按钮,点击后给父组件传点赞数,父组件显示点赞结果。
子组件(LikeButton.vue)
<template>
<button @click="handleLike">
{{ isLiked ? '取消点赞' : '点赞' }}
</button>
</template>
<script>
export default {
data() {
return {
isLiked: false,
likeCount: 0
}
},
methods: {
handleLike() {
this.isLiked = !this.isLiked;
this.likeCount = this.isLiked ? this.likeCount + 1 : this.likeCount - 1;
// 触发自定义事件,把点赞数传给父组件
this.$emit('like-change', this.likeCount);
}
}
}
</script>
父组件(Post.vue)
<template>
<div class="post">
<h3>我的动态</h3>
<p>点赞数:{{ totalLikes }}</p>
<LikeButton @like-change="updateLikes" />
</div>
</template>
<script>
import LikeButton from './LikeButton.vue'
export default {
components: { LikeButton },
data() {
return {
totalLikes: 0
}
},
methods: {
updateLikes(count) {
this.totalLikes = count;
}
}
}
</script>
<style scoped>
.post {
border: 1px solid #eee;
padding: 20px;
margin: 10px;
}
button {
padding: 6px 12px;
cursor: pointer;
}
</style>
运行后,点击“点赞”按钮,子组件的likeCount变化,通过$emit('like-change')传给父组件,父组件更新totalLikes并显示,这个案例里,events负责子→父的通信,props(如果有的话)负责父→子,分工明确~
看完这些,是不是觉得Vue2的events没那么难了?自定义事件是组件通信的关键工具,核心是“子$emit发消息,父@事件名收消息”,记住事件名规范、参数传递、跨层级通信的技巧,再避开常见的大小写、销毁事件这些坑,你就能轻松用events搞定组件间的互动啦~ 要是还有疑问,评论区随时问,咱们一起折腾代码~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。