無處不在的發佈訂閱模式 —— 此次必定

前言

發佈-訂閱模式又叫觀察者模式,它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知。javascript

它不是某一種具體的實現,而是一個計算機語言開發的一種模式,舉個鮮活的例子。html

遙控炸彈就是「發佈訂閱」的一種生活中的應用,你把炸彈 💣 埋在某輛車底,而後坐在車對面的星巴克喝咖啡,一旦獵物上車,你按下按鈕,炸彈爆炸。這一整個過程當中,炸彈「訂閱」了你,而「發佈」的權利在你手上的按鈕。前端

前端領域的應用

做爲一個前端開發,其實你已經用上了「發佈訂閱」的設計模式,不信你看下面這段代碼:vue

document.body.addEventListener('click', () => {
  console.log('監聽點擊事件')
})
複製代碼

上述代碼經過 addEventListener 方法訂閱了 body 的點擊事件,點擊任何 body 內的標籤,都會觸發回調函數的執行。這就是事件委託的原理所在, jQuery 在這方面的實現也相似以下所示:java

$('.demo').on('click', () => {
  // dosomethiong
})
複製代碼

「發佈訂閱」模式還有一個比較經典的應用是 Vue 2.x 中的雙向綁定原理 Object.defineProperty,看下面代碼:設計模式

const obj = { name: 'Nick' }
Object.defineProperty(obj, 'name', {
  set: function () {
    console.log('觸發更新')
  }
})
複製代碼

代碼中訂閱了 name 屬性,一旦它發生變化, set 函數便會執行。一樣咱們不用去關心 name 屬性在何時會發生變化,只要它敢變, set 就會被觸發。數組

再講一個 Vue 開發中你們時常會寫到的一種「發佈訂閱」模式:瀏覽器

<Child @submit="sendPost"></Child>
複製代碼

相信寫過 Vue 的同窗都不陌生,這是組件間的方法傳值,一點子組件內經過 emit 方法發佈 submit,父組件的 sendPost 方法就會被觸發。markdown

因此「發佈訂閱」模式在前端領域的應用已經達到了登峯造極的境界,在此就再也不一一舉例了,再舉下去就要不舉了。函數

手寫一個簡易 EventBus

簡單描述一下需求,EventBus 類中拋出 3 個方法,分別是:

  • on:訂閱方法,在某個組件或者頁面引入 on 方法,定義觸發的函數方法。
  • emit:觸發方法,根據上面的訂閱方法,觸發它。
  • off:銷燬訂閱的類型,相似 document.removeEventListener 。

抄傢伙,開整

class EventBus {
  constructor() {
    this.handleMaps = {} // 初始化一個存放訂閱回調方法的執行棧
  }
  
  // 訂閱方法,接收兩個參數
  // type: 類型名稱
  // handler:訂閱待執行的方法
  on(type, handler) {
    if (!(handler instanceof Function)) {
      throw new Error('別鬧了,給函數類型') // handler 必須是可執行的函數
    }
    // 若是類型名不存在,則新建對應類型名的數組
    if (!(type in this.handleMaps)) {
      this.handleMaps[type] = []
    }
    // 將待執行方法塞入對應類型名數組
    this.handleMaps[type].push(handler)
  }
  // 發佈方法,接收兩個參數
  // type:類型名稱
  // params:傳入待執行方法的參數
  emit(type, params) {
    if (type in this.handleMaps) {
      this.handleMaps[type].forEach(handler => {
        // 執行訂閱時,塞入的待執行方法,而且帶入 params 參數
      	handler(params)
      })
    }
  }
  // 銷燬方法
  off(type) {
    if (type in this.handleMaps) {
      delete this.handleMap[type]
    }
  }
}

export default new EventBus()
複製代碼

簡單的編寫了一個迷你 EventBus,核心思想即是如此。

應用於實踐

高低總要驗證一下好很差用吧!! 接下來咱們經過 Vue CLI 初始化一個基礎項目,將上述編寫的代碼引入。如圖所示: image.png

新建 utils/event_bus.js,存放上述編寫的代碼。

驗證一:父子組件通訊

修改 Home.vue 以下所示:

<template>
  <div class="home">
    技能:{{ skill }}
    <Child />
  </div>
</template>

<script> import Child from '@/components/Child' import eventBus from '@/utils/event_bus' import { onMounted, ref } from 'vue' export default { name: 'Home', components: { Child }, setup() { const skill = ref('') onMounted(() => { // 訂閱 skill 類型名 eventBus.on('skill', (key) => { skill.value = key console.log('key', key) }) }) return { skill } } } </script>
複製代碼

添加 components/Child.vue ,以下所示:

<template>
  <div>
    <button @click="play">釋放子技能</button>
    <Grandson />
  </div>
</template>

<script> import eventBus from '@/utils/event_bus' export default { name: 'Child', setup() { const play = () => { // 發佈 skill 類型方法,而且傳參數 eventBus.emit('skill', '獅子歌歌') } return { play } } } </script>
複製代碼

咱們來看看瀏覽器展示效果: 很明顯,點擊「釋放子技能」按鈕,觸發了訂閱的 skill 事件。

驗證二:爺孫組件通訊

咱們再添加一個孫組件 components/Grandson.vue ,代碼以下:

<template>
  <div>
    <button @click="play">釋放孫技能</button>
  </div>
</template>

<script> import eventBus from '@/utils/event_bus' export default { name: 'Grandson', setup() { const play = () => { eventBus.emit('skill_2', '三千煩惱') } return { play } } } </script>
複製代碼

Child.vue 組件添加以下代碼:

<template>
	...
  <Grandson />
</template>
<script> import Grandson from './Grandson' export default { name: 'Child', components: { Grandson } } </script>
複製代碼

咱們再來看看瀏覽器展現效果:

驗證三:跨組件通訊

這個纔是 EventBus 要解決的問題,修改項目原有的 views/About.vue 組件代碼以下:

<template>
  <div class="about">
    <button @click="play">釋放技能</button>
  </div>
</template>

<script> import eventBus from '@/utils/event_bus' export default { name: 'About', setup() { const play = () => { eventBus.emit('skill', '跨組件的獅子歌歌') } return { play } } } </script>
複製代碼

瀏覽器展現以下:

總結

市面上的狀態管理插件,不管是 Vuex、Redux、Mobx 等,都用到了「發佈訂閱」模式。它的設計思路值得咱們去深思和探索,上述手寫的建議 EventBus 是通用的,不管是 Vue、React、Angular 或者是原生項目,都適用。

相關文章
相關標籤/搜索