深入理解Vue $on、$emit、$off

缘起

遇到一个问题(vue2.x版本下):

A组件、D组件需要通信,考虑到嵌套的层级比较多,所以采用了eventBus的方式传递数据。

D组件因为某些操作触发了组件更新后,无法再成功监听到来自A组件传递的数据。

排查发现,问题出在了D组件移除事件的调用方式上,上述的写法中,只提供了事件,Vue会移除该事件所有的监听器

修改D组件监听及移除事件的方式,问题成功解决。

所以在这个问题的场景里,移除事件监听器时,需要提供回调函数,只移除这个回调的监听器,才能保证下一次更新组件时能正常监听到事件。

回顾

查阅Vue官方文档(Vue2.x),回顾下Vue提供的三个API的功能特性:

触发事件

vm.$emit( eventName, [...args] )

触发当前实例上的事件,eventName为事件名称,args为需要传递的参数、数据。

监听事件

vm.$on( event, callback )

监听当前实例上的自定义事件, event事件名需与触发事件时定义的事件名相同; callback为回调函数,在里面可以执行监听到事件后需要执行的操作。

移除事件

vm.$off( [event, callback] )

移除自定义事件监听器。这里提供了三种使用场景:

1.不提供参数,则移除所有的事件监听器;

bash 复制代码
vm.$off();

2.如果只提供了事件,则移除该事件所有的监听器;

bash 复制代码
vm.$off(eventName);

3.如果同时提供了事件与回调,则只移除这个回调的监听器。

bash 复制代码
vm.$off(eventName, callback);

深入

查看下Vue(这里基于2.7.14)源码中对上述三个API的实现方式:

$emit

  • 遍历回调函数列表
  • 调用每个回调函数并传递参数

$on

  • 如果传入的事件是数组,则遍历数组,并对每个子项调用$on
  • 如果传入的是单个事件:
    • 如果传入的事件不存在,创建一个新数组,并添加回调函数
    • 如果传入的事件已经存在,则将新传入的回调函数添加到事件数组列表中

$off

  • 如果传入的事件是数组,则遍历数组,并对每个子项调用$off
  • 判断是否传入了指定的回调函数,如果没传入,则移除所有监听器。若传入了,则移除指定回调的监听器

模拟实现事件总线

kotlin 复制代码
class Bus {
    constructor() {
        this.busEvent = {};
        this.$on = (name, func) => {
            if (this.busEvent[name] ) {
                this.busEvent[name][Symbol.for(func)] = func;
            } else {
                this.busEvent[name] = {}
                this.busEvent[name][Symbol.for(func)] = func;
            }
        }
        this.$off = (name, func) => {
            if (func) {
                this.busEvent[name][Symbol.for(func)] = undefined
            } else {
                this.busEvent[name] = undefined
            }
        }
        this.$emit = (name) => {
            if (!this.busEvent[name]) return
            Object.getOwnPropertySymbols(this.busEvent[name]).forEach(el => {
                console.log(this.busEvent[name][el], 'hihihi')
                if (this.busEvent[name][el] !== undefined) this.busEvent[name][el]()
            })
        }
    }
}

const bus = new Bus()
bus.$on('test', () => {
    console.log('func1111')
})
let func2 = () => {
    console.log('func222222')
}

bus.$emit('test')
bus.$on('test', func2)
bus.$off('test', func2)
// bus.$off('test', () => {
//     console.log('func222222')
// })

Vue3.x中的变更

在Vue3.x版本下,$on$off$once 实例方法已被移除,组件实例不再实现事件触发接口。$emit 仍然包含于现有的 API 中,因为它用于触发由父组件声明式添加的事件处理函数

这意味着在Vue3.x版本中,Vue不再支持使用事件总线的方式进行组件间的通信,需要借助第三方的库实现。例如 mitttiny-emitter

另外,作者也表示,在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。

可以使用一些替代方案。例如provide / injectPinia

相关推荐
晴殇i9 分钟前
CSS 相对颜色:告别 180 个颜色变量的设计系统噩梦
前端·css
MegatronKing10 分钟前
Reqable 3.0版本云同步的实践过程
前端·后端·测试
李剑一11 分钟前
我用Trae生成了一个Echarts 3D柱状图的Demo
前端·vue.js·trae
Crystal32812 分钟前
3D实战案例(飞行的火箭/创建3D导航/翻书效果/创建长方体/环环相扣效果)
前端·css
6***x54512 分钟前
前端组件库发展趋势,原子化CSS会成为主流吗
前端·css
00后程序员张13 分钟前
接口调试从入门到精通,Fiddler抓包工具、代理配置与HTTPS抓包实战技巧
前端·ios·小程序·https·fiddler·uni-app·webview
快手技术15 分钟前
闪耀NeurIPS 2025!快手13篇论文入选,Spotlight 成果跻身前三!
前端·后端
一 乐23 分钟前
宠物猫店管理|宠物店管理|基于Java+vue的宠物猫店管理管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·后端·宠物管理
熊猫比分管理员37 分钟前
【全栈源码解决方案】Vue+Java四端齐全,一周交付可运行项目!
java·前端·vue.js
o***741739 分钟前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx