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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。