前言
各種技術(shù)框架,比如 vue、react 和小程序,實(shí)現(xiàn)父子組件和兄弟組件通信的方案有很多,大多方案都強(qiáng)依賴于框架本身。這里介紹一種通過發(fā)布和訂閱的方式來(lái)實(shí)現(xiàn)組件通信的方案,純 JavaScript 實(shí)現(xiàn),可以適用于各種框架。
發(fā)布訂閱模式
發(fā)布訂閱模式包含三部分內(nèi)容,發(fā)布者、訂閱者和數(shù)據(jù)處理中心。訂閱者把自己想監(jiān)聽的事件和回調(diào)函數(shù)信息寫入到數(shù)據(jù)處理中心去;當(dāng)事件觸發(fā)時(shí),發(fā)布者發(fā)布該事件到數(shù)據(jù)處理中心,由數(shù)據(jù)處理中心統(tǒng)一執(zhí)行訂閱者寫入到數(shù)據(jù)處理中心對(duì)應(yīng)事件的回調(diào)函數(shù)。
比如A組件需要向B組件傳遞數(shù)據(jù),通過發(fā)布訂閱模式來(lái)實(shí)現(xiàn)的思路是:
- 在B組件里添加(on,事件監(jiān)聽)一個(gè)事件訂閱,監(jiān)聽的事件名稱和回調(diào)函數(shù);
- 當(dāng)A組件需要給B組件傳遞數(shù)據(jù)時(shí),可發(fā)布(emit)對(duì)應(yīng)的事件名稱并攜帶相應(yīng)的數(shù)據(jù),數(shù)據(jù)處理中心根據(jù)相應(yīng)的事件執(zhí)行回調(diào)函數(shù)并傳遞數(shù)據(jù)給B組件
定義數(shù)據(jù)處理中心
用來(lái)存儲(chǔ)事件和回調(diào)函數(shù)信息
class Emitter {
constructor() {
// 數(shù)據(jù)處理中心,用來(lái)存儲(chǔ)事件和回調(diào)函數(shù)信息
this.handlers = {}
}
}
實(shí)現(xiàn)訂閱功能
需要傳入兩個(gè)參數(shù),訂閱的事件名稱和事件觸發(fā)時(shí)的回調(diào)函數(shù)
// 訂閱
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)
}
實(shí)現(xiàn)發(fā)布功能
遍歷數(shù)據(jù)處理中心的數(shù)據(jù),找到并執(zhí)行對(duì)應(yīng)事件名稱的回調(diào)函數(shù)
// 發(fā)布
emit(eventName) {
const fns = this.handlers[eventName]
if (fns && fns.length) {
// arguments,攜帶一些數(shù)據(jù)信息;將arguments函數(shù)參數(shù)列表(類數(shù)組對(duì)象)轉(zhuǎn)為數(shù)組
const args = [].slice.call(arguments)
args.shift() // 參數(shù)去掉事件名稱
fns.forEach((fn) => {
// 給回調(diào)方法傳參
fn.apply(null, args)
})
}
}
取消訂閱
刪除數(shù)據(jù)處理中心數(shù)組中對(duì)應(yīng)事件的回調(diào)函數(shù)
// 注銷訂閱
off(eventName, fn) {
// 若是沒有傳參,注銷所有的訂閱
if (!arguments.length) {
this.handlers = {};
return this;
}
const fns = this.handlers[eventName]
if (!fns) return
// 若是只傳eventName,不傳fn,刪除對(duì)應(yīng)事件名稱下的所有回調(diào)函數(shù)
if (arguments.length === 1) {
delete this.handlers[eventName]
return
}
if(fns && fns.includes(fn)){
fns.splice(fns.indexOf(fn), 1)
}
}
完整代碼如下:
class Emitter {
constructor() {
// 數(shù)據(jù)處理中心,用來(lái)存儲(chǔ)事件和回調(diào)函數(shù)信息
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)
}
// 發(fā)布
emit(eventName) {
const fns = this.handlers[eventName]
if (fns && fns.length) {
// arguments,攜帶一些數(shù)據(jù)信息;將arguments函數(shù)參數(shù)列表(類數(shù)組對(duì)象)轉(zhuǎn)為數(shù)組
const args = [].slice.call(arguments)
args.shift() // 參數(shù)去掉事件名稱
fns.forEach((fn) => {
// 給回調(diào)方法傳參
fn.apply(null, args)
})
}
}
// 注銷訂閱
off(eventName, fn) {
// 若是沒有傳參,注銷所有的訂閱
if (!arguments.length) {
this.handlers = {};
return this;
}
const fns = this.handlers[eventName]
if (!fns) return
// 若是只傳eventName,不傳fn,刪除對(duì)應(yīng)事件名稱下的所有回調(diào)函數(shù)
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組件傳遞數(shù)據(jù)
- B組件
// B組件
import emitter from 'emitter'
export default {
data() {
return {
pageIndex: 1
}
},
created() {
// 添加'getAdata'事件訂閱
emitter.on('getAData' + this.pageIndex, this.getAData.bind(this))
},
// 路由頁(yè)面銷毀
destroyed() {
emitter.on('off' + this.pageIndex)
},
methods: {
getAData(data) {
console.log(data.info, '獲取到A組件傳給B組件的數(shù)據(jù)')
}
}
}
- A組件
// A組件
import emitter from 'emitter'
export default {
data() {
return {
pageIndex: 1
}
},
created() {
setTimeout(() => {
// 發(fā)布'getAdata'事件并攜帶數(shù)據(jù)
emitter.eimit('getAData' + this.pageIndex, {
info: 'A組件發(fā)送給B組件的數(shù)據(jù)'
})
}, 2000)
}
}