前端小纠结--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

欢迎加入群聊

若是入群失败,添加我的微信,拉你入群,验证消息:前端交流

关注微信公众号,发现更多精彩内容

微信公众号
相关文章
相关标签/搜索