美文网首页
前端微服务化解决方案4 - 消息总线

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

作者: Hello丶Alili | 来源:发表于2018-10-26 11:06 被阅读53次

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

    黑盒子

    问题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
      }
    }
    
    // 向外输出 Reducer
    export const storeInstance = createStore(combineReducers({ namespace: () => 'base', render }))
    
    

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

    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来导入模块的对外输出的Reducer(后续会被称作模块对外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示例源码

    微前端子项目示例源码

    相关文章

      网友评论

          本文标题:前端微服务化解决方案4 - 消息总线

          本文链接:https://www.haomeiwen.com/subject/ejjktqtx.html