解读Vue3中废弃组件事件的$on,$off 和 $once 实例方法
本文要讲解的内容原文档地址点我传送
Vue3.0中事件API
$on,$off 和 $once 实例方法已被移除,应用实例不再实现事件触发接口。
接下来我们分析下为什么要在Vue3中去掉,如果需要继续使用此功能改为使用第三方 mitt 库替代。
2.x语法
在 2.x 中,Vue 实例可用于触发通过事件触发 API 强制附加的处理程序 ($on,$off 和 $once),这用于创建 event hub,以创建在整个应用程序中使用的全局事件侦听器:
eventHub.js
- // eventHub.js
- const eventHub = new Vue()
- export default eventHub
复制代码 ChildComponent.vue
- // ChildComponent.vue
- import eventHub from './eventHub'
- export default {
- mounted() {
- // 添加 eventHub listener
- eventHub.$on('custom-event', () => {
- console.log('Custom event triggered!')
- })
- },
- beforeDestroy() {
- // 移除 eventHub listener
- eventHub.$off('custom-event')
- }
- }
复制代码 ParentComponent.vue
- // ParentComponent.vue
- import eventHub from './eventHub'
- export default {
- methods: {
- callGlobalCustomEvent() {
- eventHub.$emit('custom-event') // 如果ChildComponent mounted,控制台中将显示一条消息
- }
- }
- }
复制代码 上述代码示例中 ChildComponent.vue 和 ParentComponent.vue 都引用了同一个 Vue 的实例对象 eventHub。使用 eventHub 的目的就是在调用 $on, $off, $emit 时是同一个对象上的方法调用。
我们知道组件中的 this 也就是一个 Vue 的实例对象。
说到这里,善于思考的同学可能会想到,我们是不是可以将上述的 eventHub.xx 改为 this.xx ?
答案是不可以的,因为如此一来 ChildComponent.vue 中的 this 和 ParentComponent.vue 的 this 将不会是同一个 Vue 实例对象,也就是说 (new ChildComponent) !== (new ParentComponent)
上述代码实际可以改为如下
ChildComponent.vue
- // ChildComponent.vue
- export default {
- mounted() {
- // 添加 eventHub listener
- this.$parent.$on('custom-event', () => {
- console.log('Custom event triggered!')
- })
- },
- beforeDestroy() {
- // 移除 eventHub listener
- this.$parent.$off('custom-event')
- }
- }
复制代码 ParentComponent.vue
- // ParentComponent.vue
- export default {
- methods: {
- callGlobalCustomEvent() {
- this.$emit('custom-event') // 如果ChildComponent mounted,控制台中将显示一条消息
- }
- }
- }
复制代码 父组件使用 this.$emit 去触发事件,子组件中使用 this.$parent 去获取父组件的实例对象。但如果是多层级的子孙组件,这就比较难受了,需要 this.$parent.$parent…。
所以在Vue2.x中 this.$on this.$off this.$emit 通常只是在一个组件内进行使用,父子组件使用的时候要考虑实例对象是否是同一个。而在一个组件内的时候,我们可以直接this.来调用其他方法,不需要这么麻烦。因此将这几个方法从Vue3.0中移除。$emit 依旧是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序
Vue3.x推荐使用外部库mitt来代替 $on $emit $off
接下来部分是解读 mitt 源码
mitt源码使用的是typescript编写的,源码加注释一共不到90行,阅读起来比较轻松。typescript不是本次的重点,所以我将mitt源码以js的形式展示如下。
- /**
- * 向外暴露的默认函数
- * @param 入参为 EventHandlerMap 对象 (ts真香,我们能清楚的知道参数的类型是什么,返回值是什么)
- * @returns 返回一个对象,对象包含属性 all,方法 on,off,emit
- */
- export default functiоn mitt (all) {
- /*
- 此处实参可传一个EventHandlerMap对象,实现多个 mitt 的合并。例如:
- const m1 = mitt();
- m1.on('hi', () => { console.log('Hi, I am belongs to m1.'); });
- const m2 = mitt(m1.all);
- m2.emit('hi') // Hi, I am belongs to m1.
- m2.on('hello', () => { console.log('Hello, I am belongs to m2.'); });
- m1.emit('hello'); // Hello, I am belongs to m2.
- m1.all === m2.all // true
- */
- all = all || new Map();
- return {
- // 事件键值对映射对象
- all,
- /**
- * 注册一个命名的事件处理
- * @param type 事件名,官方表示事件名如是 *,用来标记为通用事件,调用任何事件,都会触发命名为 * 的事件
- * @param handler 事件处理函数
- */
- on (type, handler) {
- // 根据type去查找事件
- const handlers = all.get(type);
- // 如果找到有相同的事件,则继续添加,Array.prototype.push 返回值为添加后的新长度,
- const added = handlers && handlers.push(handler);
- // 如果已添加了type事件,则不再执行set操作
- if (!added) {
- all.set(type, [handler]); // 注意此处值是数组类型,可以添加多个相同的事件
- }
- },
- /**
- * 移除指定的事件处理
- * @param type 事件名,和第二个参数一起用来移除指定的事件,
- * @param handler 事件处理函数
- */
- off (type, handler) {
- // 根据type去查找事件
- const handlers = all.get(type);
- // 如果找到则进行删除操作
- if (handlers) {
- // 这里用了个骚操作,其实就是找到了,则删除(多个相同的只会删除找到的第一个),没找到则不会对原数组有任何影响
- handlers.splice(handlers.indexOf(handler) >>> 0, 1);
- }
- },
- /**
- * 触发所有 type 事件,如果有type为 * 的事件,则最后会执行。
- * @param type 事件名
- * @param evt 传递给处理函数的参数
- */
- emit (type, evt) {
- // 找到type的事件循环执行
- (all.get(type) || []).slice().map((handler) => { handler(evt); });
- // 然后找到所有为*的事件,循环执行
- (all.get('*') || []).slice().map((handler) => { handler(type, evt); });
- }
- };
- }
复制代码 代码还是相当的精简的,麻雀虽小,五脏俱全。
接下来我们写几个例子来小牛试刀。
- // emit 不是单例
- const m1 = mitt();
- const m2 = mitt();
- m1 === m2; // false
- // 多个mitt之间可以实现合并,合并后的mitt的all属性指向的是同一个内存地址
- const m3 = mitt(m2.all);
- m3.all === m2.all; // true
- m2.on('hi', () => { console.log('我是m2'); });
- m3.emit('hi'); // 我是m2
- m2.on('*', () => { console.log('我是公共的'); });
- m2.emit('hi');
- // 我是m2
- // 我是公共的
复制代码 到此我们已经学会如果使用 emitt 以及如何使用它了。
那么这里留个作业给大家发挥下,我们如何基于它实现一个单例的 mitt 呢?
原创文章,作者:starterknow,如若转载,请注明出处:https://www.starterknow.com/126925.html