事件传递机制
一、从Vue的事件传递说起
组件间的信息传递是Vue框架中非常核心的部分,大多数情况下可以Prop/事件
进行父子传递, Provide/inject
允许一个Prop/事件内容传递或者组件与组件间的跨级传递
, 或者使用笨重的vuex/pinia
进行状态管理
但是Vue2.x
中是存在有中央事件总线的, 即采用公共的Vue实例进行传递数据, emit/on
发射和监听自定义事件, $off
销毁事件, Vue3.x
中因为创建实例使用creatApp({})
的方法没办法和之前new Vue({})
一样在实例上挂载, 所以官方建议mitt
或者tiny-emitter
三方库
优点: 三方库体积小, 传递方便
缺点: 状态无法记录, 不方便管理
二、从mitt入门事件传递
mitt库核心代码如下:
module.exports = function _mitt (n) {
return {
/**
* 实例一个Map结构的 n 管理订阅者
*/
all: (n = n || new Map()),
/**
* @param e代表事件名
* @param t代表传入的fn回调
*/
on: function (e, t) {
let i = n.get(e);
(i && i.push(t)) || n.set(e, [t])
},
/**
* @param e为string或Symbol的事件名 也可以为 *
* @param t为传递的参数
*/
emit: function (e, t) {
(n.get(e) || []).slice().map(item=>item(t)),
(n.get("*") || []).slice().map(item=>item(e,t));
},
/**
* @param e为事件名
* @param t为需要对比的回调函数
*/
off: function (e, t) {
let i = n.get(e);
let idx = i.indexOf(t);
if(idx===0){
i.splice(idx, 1)
}else{
(idx !== -1) ? (i && i.splice(idx, 1)) : ''
}
}
}
}
不得不说, 真的十分精简!
但是提供的api
还是不太齐全, 继续往下看
三、深入事件传递
在很多情况下,事件传递还会提供once/hasListener...
这样的api
以满足更复杂的业务场景
事件传递实现的流程:
事实上, 从mitt的代码可以看出事件传递时实际上是先订阅再发布的
- 全局创建存储事件的集合, 事件名为key, 对应的值为value
- 首先需要订阅或者监听emit发射的某个事件, 参数为事件名和callback
- emit发射事件, 参数为事件名和被传递的数据data
- 经过3的
callback.call(null, data)
完成数据接收
弄清楚事件传递流程之后, 接下来就想办法实现一些其他的比较重要的api
四、实现一个完整的事件分发机制
参考nodejs
的events
模块实现一些常用的事件分发api
export default class EventEmitter{
//1.初始化events对象
constructor(){
this._events = new Map()
}
private _events
//2.创建一些类型检测的工具函数
private function _toString(){
return Object.prototype.toString
}
private function _isType(obj){
return this._toString.call(obj).slice(8,-1).toLowerCase()
}
private function _isArray(obj){
return Array.isArray(obj) || this._isType(obj) === 'array'
}
private function _isNullOrUndefined(obj){
return obj === null || obj === undefined
}
private function _addListener(type,fn,ctx,once?){
//2.1 内部实现订阅
if(typeof fn !== 'function'){
throw new Error('fn must be a function!')
}
fn.context = ctx
fn.once = !!once
const events = this._events.get(type)
//2.2 根据event类型不同做不同处理
if(this._isNullOrUndefined(events)){
this._events.set(type,fn)
return true
}else if(typeof events === 'function'){
const activeListenFns = [events]
activeListenFns.push(fn)
this._events.set(type,activeListenFns)
return true
}else if(this._isArray(events)){
const activeListenFns = events.push(fn)
this._events.set(type,activeListenFns)
return true
}else{
return false
}
}
// 3.生成暴露出去的API函数
addListener(type,fn,context){
//3.1 收集订阅
return this._addListener(type,fn,context)
}
once(type,fn,context){
//3.2 只订阅本次 下次取消订阅
return this._addListener(type,fn,context,true)
}
emit(type,...rest){
//3.3 发布事件并传递数据
if(this._isNullOrUndefined(type)){
throw new Error('emit must receive at least one argument!')
}
const events = this._events.get(type)
if(this._isNullOrUndefined(events)) return false
if(typeof events === 'function'){
events.call(events.context || null, rest)
events.once? this.removeListener(type,event) : ''
}else if(this._isArray(events)){
events.map(cb=>{
cb.call(cb.context || null, rest)
cb.once? this.removeListener(type,cb) : ''
})
}
return true
}
removeListener(type,fn){
//3.4 移除对某个事件类型的某个监听
if(this._isNullOrUndefined(this._events) || this._isNullOrUndefined(type)) return false
if(typeof fn !== 'function'){
throw new Error('fn must be a function')
}
const events = this._events.get(type)
if(typeof events === 'function'){
events === fn && this._events.remove(type)
}else{
const needToRemoveFnIdx = events.findIndex(e=> e===fn)
needToRemoveFnIdx === -1? return false : events.splice(needToRemoveFnIdx,1)
}
return true
}
removeAllListeners(type){
//3.4 移除对某个事件类型的全部监听
if(this._isNullOrUndefined(this._events)) return false
if(this._isNullOrUndefined(type)){
this._events = new Map()
return false
}
const events = this._events.get(type)
if(!this._isNullOrUndefined(events)){
this._events.remove(type)
}
return true
}
}
以上就实现了一个具有once/removeListener/removeAllListeners
的事件分发类, 当然如果还有其他需求可以继续添加功能( *比如获取某个事件类型的全部监听/监听总数... *), 这里只是把实现的流程和思想分享一下, 想要学习更多的话推荐参考nodejs
中的events
模块和eventmitter3
库, 尤其这个第三方库现在每周都有200W+的下载量, 十分推荐手敲一遍