likes
comments
collection
share

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现

作者站长头像
站长
· 阅读数 14

前言

清单

  • 了解mitt、tiny-emitter的作用及使用场景
  • 了解vue2中非父子通信的使用和实现方式
  • 学习发布订阅模式实现

mitt

  • 从github仓库中下载mitt源码,git clone https://github.com/developit/mitt.git
  • 从实现的角度看,mitt完全使用ts实现,这样的实现有个好处,一方面不用单独再写一份ts类型导出,另一方面在别人使用的时候,有很友好的ts提示
  • 是什么?一个可供浏览器和nodejs使用的体积小于200B的事件发布/订阅库
  • 用来干什么?组件通信、模块通信、异步编程通信、插件系统通信、单元测试等场景,主要用来通信
  • 如何使用?
npm install --save mitt

import mitt from 'mitt'

const emitter = mitt() 
// on用来监听事件
emitter.on('foo', e => console.log('foo', e) )

// *可以监听所有事件
emitter.on('*', (type, e) => console.log(type, e) )

// 触发事件
emitter.emit('foo', { a: 'b' })

// 清除所有事件
emitter.all.clear() 
  • 不同页面或函数中进行事件通信,就像vue中的$emit和$on事件函数一样
  • 因为mitt使用的普通的函数封装,故其所有的数据都是以map的方式存储,所有方法共享和共同维护这一份数据~

tiny-emitter

  • 仓库源码地址?git clone https://github.com/scottcorgan/tiny-emitter.git
  • 是什么?一个轻量级(体积小于1k)的事件发布/订阅库
  • 用来干什么?同mitt一样
  • 什么环境可以用?浏览器和 Node.js 环境都可用
  • 如何使用?
    npm install tiny-emitter --save
    
    // 这种是自己new Emitter实例化对象的方式使用
    var Emitter = require('tiny-emitter');
     var emitter = new Emitter(); 
     emitter.on('some-event', function (arg1, arg2, arg3) { }); 
     emitter.emit('some-event', 'arg1 value', 'arg2 value', 'arg3 value');
    
    // 这种是通过tiny-emitter创建过实例化的方式使用的
    var emitter = require('tiny-emitter/instance');
    emitter.on('some-event', function (arg1, arg2, arg3) { });
    emitter.emit('some-event', 'arg1 value', 'arg2 value', 'arg3 value');
    
  • 可用aip?on,once,off,emit这四个方法
  • 具体源码实现和vue的eventsMixin中的封装的函数实现类似,具体继续向下看~

vue2的非父子通信$emit,$on

相信只要是用vue框架写业务的同学们,大部分都会遇到不同组件之间、兄弟组件之间的通信问题,也会用到on,on,on,emit,$off等函数,接下来我们就来探究下我们常用的函数到底是如何实现的?

先说使用,其实vue2的使用非常简便

import Vue from 'vue'
export const EventBus = new Vue()

// 在需要监听的组件中引入导出的EventBus
import { EventBus } from "xxxx"
EventBus.$on('onMsg',(data)=>{})

// 在需要发送信息的组件中也引入导出的EventBus
import {EventBus} from "xxxx"
EventBus.$emit('onMsg','vue $emit')

上述使用中,其实质还是Vue创建实例后,同一个实例上的属性和方法之间数据是公用的~

接下来看下源码,本次源码参照版本vue2.6,其目录src/core/instance/events.js文件中eventsMixin方法里面实现了$emit,$on,$once,$once这四个方法,如下图

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现 关于 $on,这里的实现还是很简明的,就是添加到_events对象上;

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现

接下来看下$emit方法:

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现 可以看出来三个点:

  • 事件触发只支持单个event入参
  • 如果event中有多个fn函数,则采用for的形式循环执行,执行用到了apply和call的,选择比较巧妙
  • vue中的执行比较严谨,对错误的处理比较多

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现 接下来看下函数不用了是如何卸载的($off):

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现 由于vue中支持event可以传入多个event,$off也支持array方式的入参,所以上图中考虑了很多情况的边界条件;

最后来看下$once,说实话,我是不怎么用这个函数,不知道大家常用不?大家要是有项目使用场景,欢迎在评论区留言,让我也学习学习~

【源码共读】第8期 | 从mitt、tiny-emitter 发布订阅源码看vue非父子通信实现 看了vue中这四个函数的实现,感觉和tiny-emitter源码有很多相似的地方,但也根据自己的业务,扩展了其功能和入参,就比如event: string | Array<string>

三者对比

标题使用场景方法相同点不同点
mitt不同组件、事件、模块、异步编程间的通信on、 off 、emit、mitt、 all实现方式不同;支持通配符*监听所有事件;通过普通函数的方式返回几个api方法
tiny-emitter同mitt一样on、once、emit、off1.用于组件间通信;2.都使用了订阅发布模式;3.都有on、emit、off方法;轻量级;基于构造函数的方式实现,所有方法都挂载其原型上公用;添加了once方法;支持改变当前函数的this指向;支持链式调用
vue不同组件之间的信息通信$on、$once、$emit、$off基于vue框架主要解决不同组件之间通信问题;$on支持array方式的入参;重量级,不能脱离Vue框架;支持链式调用

发布订阅模式实现

上面三个库的代码都用到了事件触发和事件监听的方式,其实也是常见的订阅发布设计模式,👉又叫观察者模式!

那么它们之间是如何分工的呢?

订阅者:向发布者注册自己的兴趣,当发布者状态发送变化时接收通知,即订阅通过on对象用eventName表示;

发布者:维护一组(即on,emit都为同一个eventName)订阅者,并在状态发生变化时通知所有订阅者更新自己的状态,方便灵活通信。

在nodejs中的事件驱动模型中EventEmitter 类实现也是采用事件的发布和订阅模式。

总结

通过对mitttiny-emittervue的eventsMixin源码的学习,了解到常用的$on和$emit是用到了设计模式中的发布订阅模式(即观察者模式),原来设计模式离我们开发并不遥远,源码阅读过程中,发现即使不同的项目在关于某些相同的东西的实现上也是有很多相似之处的,好的源码和思想会有相通之处。