前端小糾結--Event Bus模塊約定

以前的約定--前端小糾結--Vue項目代碼組織和風格約定javascript

背景

本文產生的背景是,由於在封裝Axios的時候,對於服務端的異常或者須要根據業務上異常code,進行不一樣的處理邏輯。可是基於Axios作的封裝做爲跨項目、跨框架模塊使用因此不能和具體的router或者store等模塊耦合,因此使用Event Bus,在整個Web範圍中來解耦各個組件。html

爲何使用Event Bus?

Event Bus有特殊的使用場景,不止在view組件之間的通訊使用;Event Bus設計做爲整個SPA應用的事件(消息)投遞層使用。前端

  • 模塊通訊vue

    解決模塊之間的通訊問題,view組件層面,父子組件、兄弟組件通訊均可以使用event bus處理。html5

  • 模塊解耦java

    storage change事件,cookie change事件,view組件的事件等,所有轉換ios

    使用Event Bus來訂閱和發佈,這樣就統一了整個應用不一樣模塊之間的通訊接口問題。git

  • 父子頁面通訊github

    window.postMessage + Event Busweb

  • 多頁面通訊

    storage change + Event Bus

Event Bus模塊封裝

Event Bus接口設計

參考jQueryVue

方法: on, off, once, emit(可選trigger/dispatch/publish)

  • 若是你使用vue框架,就使用Vue對象或者使用dynamic-vue-bus包(其實也是包裝了下Vue,實現自動destroy handler)。
  • 若是搞一個框架無關的,兼容性好的就基於PubSubJS 封裝。

BusEvent元數據模型設計

參考DOM中的Event對象

// 服務端返回的元數據模型(responeBody)
export interface ResponseResult<T = any> {
  message: string;
  code: number;
  data: T;
}

export interface BusEvent<T = any> extends ResponseResult<T> {
  type: string;
}

export type BusEventHandler = (data: BusEvent) => any;
複製代碼

這裏的message是參考Error來設計的,由於當咱們程序異常或者業務上異常時,就能夠統一直接使用new Error()進行處理。

封裝實現

項目中實現選擇一種便可。

基於Vue封裝實現

import EventBus from 'dynamic-vue-bus';
import { isFunction } from 'lodash-es';

// 基於Vue實現
export const VueBus = {
  originBus: EventBus,
  on(topic: string | string[], handler: BusEventHandler): string[] {
    // @ts-ignore
    this.originBus.$on(topic, handler)
    return [];
  },

  off(topic: string | string[], handler?: any) {
    const length = arguments.length;
    switch (length) {
      case 1:
        this.originBus.$off(topic);
        break;
      case 2:
        this.originBus.$off(topic, handler);
        break;
      default:
        this.originBus.$off();
    }
  },

  once(topic: string, handler: BusEventHandler) {
    // @ts-ignore
    return this.originBus.$once(topic, handler);
  },

  publish(topic: string, data: BusEvent) {
    return this.originBus.$emit(topic, data);
  },
};
複製代碼

使用:

function wsHandler(evt: BusEvent) {
    console.log(evt);
}
const topic = 'ws.100021';
VueBus.on(topic, wsHandler);
// VueBus.once(topic, wsHandler);

VueBus.publish(topic, {
   code: 100021,
   type: 'ws',
   message: '',
   data: {
        result: [{
            id: 1,
            name: 'junna'
        }]
    }
});

// VueBus.off();
VueBus.off(topic);
// VueBus.off(topic, wsHandler);

// 使用原生的Vue
const eventBus = PubSubBus.originBus;
複製代碼

基於PubSubJS實現

如下代碼並沒測試

export const PubSubBus = {
  originBus: PubSub,
  on(topic: string | string[], handler: BusEventHandler): string[] {
    if (!topic) {
      return [];
    }
    // @ts-ignore
    let events: string[] = [].concat(topic);
    return events.map(evt =>
      PubSub.subscribe(evt, () => (topic: string, data: BusEvent) =>
        handler(data)
      )
    );
  },

  /** * 不兼容這種模式:PubSubBus.off('ws.001', handler); * 由於PubSubJS使用token來off這種操做 */
  off(tokenOrHandler?: () => void | string | string[]) {
    let length = arguments.length,
      evts: string[],
      listener;
    if (length === 0) {
      PubSub.clearAllSubscriptions();
    } else {
      // PubSubBus.off(handler);
      if (isFunction(tokenOrHandler)) {
        PubSub.unsubscribe(listener);
      } else {
        // @ts-ignore
        evts = [].concat(tokenOrHandler);
        evts.forEach(evt => PubSub.unsubscribe(evt));
      }
    }
  },

  once(topic: string, handler: BusEventHandler) {
    // @ts-ignore
    return PubSub.subscribeOnce(topic, handler);
  },

  publish(topic: string, data: BusEvent, sync = false) {
    return sync ? PubSub.publishSync(topic, data) : PubSub.publish(topic, data);
  },
};
複製代碼

使用:

function wsHandler(evt: BusEvent) {
    console.log(evt);
}
const topic = 'ws.100021';
const tokens: string[] = PubSubBus.on(topic, wsHandler);
// PubSubBus.once(topic, wsHandler);

PubSubBus.publish(topic, {
   code: 100021,
   type: 'ws',
   message: '',
   data: {
        result: [{
            id: 1,
            name: 'junna'
        }]
    }
});

// PubSubBus.off();
PubSubBus.off(topic);
// PubSubBus.off(tokens[0]); // 等價於VueBus.off(topic, wsHandler);
// PubSubBus.off(wsHandler); // 移除多個topic使用的同一個handler.

// 使用原生的PubSubJS
const PubSub = PubSubBus.originBus;
複製代碼

Event相關約定

BusEvent#type類型約定

事件類型的約定參考:

  • wsWebSocket事件
  • storage: 儲存事件, sessionStorage, localStorage
  • cs: cache storage
  • ap: application cache
  • cookie: cookie事件
  • biz: 業務事件
  • system: 系統事件
  • ui: 界面
  • cmp: components事件

BusEvent#code範圍約定

code範圍約定只是參考,由於這個約定須要和服務端小夥伴,甚至系統設計時規劃決定。 (瞎寫)

  • ws: 10000~19999
  • storage: 20000~29999, sessionStorage: 22000~22999, localStorage: 23000~23999
  • cs: 24000~24999
  • ap: 25000~25999
  • cookie: 26000~26999
  • biz: 30000~31000
  • system: 40000~41000
  • ui: 42000~42999
  • cmp: 43000~44999

統一處理EventBus事件

有了約定,就能夠統一發布相關的事件

// 模擬WebSocket推送消息
const data = [{
   code: 100021,
   type: 'ws',
   message: '',
   data: {
        result: [{
            id: 1,
            name: 'junna'
        }]
    }
},{
   code: 100022,
   type: 'ws',
   message: '',
   data: {
        result: [{
            id: 1,
            name: 'junna'
        }]
    }
}];

data.forEach(event => PubSubBus.publish(`${event.type}.${event.code}`), event));

複製代碼

總結

經過對Event Bus的統一封裝,對外提供統一的接口,統一整個SPA事件系統(非DOM層面),完成了模塊之間的解耦。

缺點:

  • 基於Vue封裝實現的不支持namespace
  • 基於Vue封裝實現和PubSubJS接口參數和返回值有差別,因此選擇一種便可

參考

global-event-bus

PubSubJS

dynamic-vue-bus

讓在Vue中使用的EventBus也有生命週期

Web Storage Support Test

Working with quota on mobile browsers

Browser Storage Abuser

BrowserStorageAbuser github

歡迎加入羣聊

若是入羣失敗,添加我的微信,拉你入羣,驗證消息:前端交流

關注微信公衆號,發現更多精彩內容

微信公衆號
相關文章
相關標籤/搜索