前言
各种技术框架,比如 vue、react 和小程序,实现父子组件和兄弟组件通信的方案有很多,大多方案都强依赖于框架本身。这里介绍一种通过发布和订阅的方式来实现组件通信的方案,纯 JavaScript 实现,可以适用于各种框架。
发布订阅模式
发布订阅模式包含三部分内容,发布者、订阅者和数据处理中心。订阅者把自己想监听的事件和回调函数信息写入到数据处理中心去;当事件触发时,发布者发布该事件到数据处理中心,由数据处理中心统一执行订阅者写入到数据处理中心对应事件的回调函数。
比如A组件需要向B组件传递数据,通过发布订阅模式来实现的思路是:
- 在B组件里添加(on,事件监听)一个事件订阅,监听的事件名称和回调函数;
- 当A组件需要给B组件传递数据时,可发布(emit)对应的事件名称并携带相应的数据,数据处理中心根据相应的事件执行回调函数并传递数据给B组件
定义数据处理中心
用来存储事件和回调函数信息
1 2 3 4 5 6
| class Emitter { constructor() { this.handlers = {} } }
|
实现订阅功能
需要传入两个参数,订阅的事件名称和事件触发时的回调函数
1 2 3 4 5 6 7 8
| on(eventName, fn) { if (typeof fn !== "function") { console.error('fn must be a function') } if (!this.handlers[eventName]) { this.handlers[eventName] = [] } this.handlers[eventName].push(fn) }
|
实现发布功能
遍历数据处理中心的数据,找到并执行对应事件名称的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| emit(eventName) { const fns = this.handlers[eventName] if (fns && fns.length) { const args = [].slice.call(arguments) args.shift() fns.forEach((fn) => { fn.apply(null, args) }) } }
|
取消订阅
删除数据处理中心数组中对应事件的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| off(eventName, fn) { if (!arguments.length) { this.handlers = {}; return this; } const fns = this.handlers[eventName] if (!fns) return if (arguments.length === 1) { delete this.handlers[eventName] return } if(fns && fns.includes(fn)){ fns.splice(fns.indexOf(fn), 1) } }
|
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Emitter { constructor() { this.handlers = {} } on(eventName, fn) { if (typeof fn !== "function") { console.error('fn must be a function') } if (!this.handlers[eventName]) { this.handlers[eventName] = [] } this.handlers[eventName].push(fn) } emit(eventName) { const fns = this.handlers[eventName] if (fns && fns.length) { const args = [].slice.call(arguments) args.shift() fns.forEach((fn) => { fn.apply(null, args) }) } } off(eventName, fn) { if (!arguments.length) { this.handlers = {}; return this; } const fns = this.handlers[eventName] if (!fns) return if (arguments.length === 1) { delete this.handlers[eventName] return } if(fns && fns.includes(fn)){ fns.splice(fns.indexOf(fn), 1) } } }
module.exports = new Emitter()
|
代码示例
A组件需要向B组件传递数据
- B组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import emitter from 'emitter' export default { data() { return { pageIndex: 1 } }, created() { emitter.on('getAData' + this.pageIndex, this.getAData.bind(this)) }, destroyed() { emitter.on('off' + this.pageIndex) }, methods: { getAData(data) { console.log(data.info, '获取到A组件传给B组件的数据') } } }
|
- A组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import emitter from 'emitter' export default { data() { return { pageIndex: 1 } }, created() { setTimeout(() => { emitter.eimit('getAData' + this.pageIndex, { info: 'A组件发送给B组件的数据' }) }, 2000) } }
|