通過發(fā)布訂閱模式實(shí)現(xiàn)組件間的通信

前言

各種技術(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)
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容