前端微服务化解决方案4 - 消息总线

微前端的消息总线,主要的功能是搭建模块与模块之间通讯的桥梁.

黑盒子

问题 1:

应用微服务化之后,每一个单独的模块都是一个黑盒子, 里面发生了什么,状态改变了什么,外面的模块是无从得知的. 比如模块A想要根据模块B的某一个内部状态进行下一步行为的时候,黑盒子之间没有办法通信.这是一个大麻烦.

问题 2

每一个模块之间都是有生命周期的.当模块被卸载的时候,如何才能保持后续的正常的通信?

ps. 我们必须要解决这些问题,模块与模块之间的通讯太有必要了.

打破壁垒

在 github 上single-spa-portal-example,给出来一解决方案.

基于 Redux 实现前端微服务的消息总线(不会影响在编写代码的时候使用其他的状态管理工具).

大概思路是这样的:

每一个模块,会对外提供一个 Store.js.

这个文件里面的内容,大致是这样的.

import { createStore, combineReducers } from "redux";

const initialState = {
  refresh: 0,
};

function render(state = initialState, action) {
  switch (action.type) {
    case "REFRESH":
      return { ...state, refresh: state.refresh + 1 };
    default:
      return state;
  }
}

// 向外输出 Store
export const storeInstance = createStore(
  combineReducers({ namespace: () => "base", render })
);

对于这样的代码,有没有很熟悉? 对,他就是一个普通的 Store 文件, 每一个模块对外输出的Store.js,就是一个模块的 Store.

Store.js 如何被使用?

我们需要在模块加载器中,导出这个 Store.js

于是我们对模块加载器中的Register.js文件 (该文件在上一章出现过,不懂的同学可以往回看)

进行了以下改造:

import * as singleSpa from 'single-spa';

//全局的事件派发器 (新增)
import { GlobalEventDistributor } from './GlobalEventDistributor'
const globalEventDistributor = new GlobalEventDistributor();


// hash 模式,项目路由用的是hash模式会用到该函数
export function hashPrefix(app) {
...
}

// pushState 模式
export function pathPrefix(app) {
...
}

// 应用注册
export async function registerApp(params) {
    // 导入派发器
    let storeModule = {}, customProps = { globalEventDistributor: globalEventDistributor };

    // 在这里,我们会用SystemJS来导入模块的对外输出的Store(后续会被称作模块对外API),统一挂载到消息总线上
    try {
        storeModule = params.store ? await SystemJS.import(params.store) : { storeInstance: null };
    } catch (e) {
        console.log(`Could not load store of app ${params.name}.`, e);
        //如果失败则不注册该模块
        return
    }

    // 注册应用于事件派发器
    if (storeModule.storeInstance && globalEventDistributor) {
        //取出 redux storeInstance
        customProps.store = storeModule.storeInstance;

        // 注册到全局
        globalEventDistributor.registerStore(storeModule.storeInstance);
    }

    //当与派发器一起组装成一个对象之后,在这里以这种形式传入每一个单独模块
    customProps = { store: storeModule, globalEventDistributor: globalEventDistributor };

    // 在注册的时候传入 customProps
    singleSpa.registerApplication(params.name, () => SystemJS.import(params.main), params.base ? (() => true) : pathPrefix(params), customProps);
}

全局派发器 GlobalEventDistributor

全局派发器,主要的职责是触发各个模块对外的 API.

GlobalEventDistributor.js

export class GlobalEventDistributor {
  constructor() {
    // 在函数实例化的时候,初始一个数组,保存所有模块的对外api
    this.stores = [];
  }

  // 注册
  registerStore(store) {
    this.stores.push(store);
  }

  // 触发,这个函数会被种到每一个模块当中.便于每一个模块可以调用其他模块的 api
  // 大致是每个模块都问一遍,是否有对应的事件触发.如果每个模块都有,都会被触发.
  dispatch(event) {
    this.stores.forEach((s) => {
      s.dispatch(event);
    });
  }

  // 获取所有模块当前的对外状态
  getState() {
    let state = {};
    this.stores.forEach((s) => {
      let currentState = s.getState();
      console.log(currentState);
      state[currentState.namespace] = currentState;
    });
    return state;
  }
}

在模块中接收派发器以及自己的 Store

上面提到,我们在应用注册的时候,传入了一个 customProps,里面包含了派发器以及 store. 在每一个单独的模块中,我们如何接收并且使用传入的这些东西呢?

import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import RootComponent from "./root.component";
import { storeInstance, history } from "./Store";
import "./index.less";

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: (spa) => {
    // 我们在创建生命周期的时候,把消息总线传入的东西,以props的形式传入组件当中
    // 这样,在每个模块中就可以直接调用跟查询其他模块的api与状态了
    return (
      <RootComponent
        store={spa.customProps.store.storeInstance}
        globalEventDistributor={spa.customProps.globalEventDistributor}
      />
    );
  },
  domElementGetter: () => document.getElementById("root"),
});

export const bootstrap = [reactLifecycles.bootstrap];

export const mount = [reactLifecycles.mount];

export const unmount = [reactLifecycles.unmount];

未完待续 …

相关文章

前端微服务化解决方案 1 - 思考

前端微服务化解决方案 2 - Single-SPA

前端微服务化解决方案 3 - 模块加载器

前端微服务化解决方案 4 - 消息总线

前端微服务化解决方案 5 - 路由分发

前端微服务化解决方案 6 - 构建与部署

前端微服务化解决方案 7 - 静态数据共享

前端微服务化解决方案 8 - 二次构建

Demo

前端微服务化 Micro Frontend Demo

微前端模块加载器

微前端 Base App 示例源码

微前端子项目示例源码

微信公众号

本文链接:

https://alili.tech/archive/a9a1f81b/

# 最新文章