文章轉發自 alili.tech前端
微前端的消息總線,主要的功能是搭建模塊與模塊之間通信的橋樑.react
應用微服務化以後,每個單獨的模塊都是一個黑盒子, 裏面發生了什麼,狀態改變了什麼,外面的模塊是無從得知的. 好比模塊A
想要根據模塊B
的某一個內部狀態進行下一步行爲的時候,黑盒子之間沒有辦法通訊.這是一個大麻煩.git
每個模塊之間都是有生命週期的.當模塊被卸載的時候,如何才能保持後續的正常的通訊?github
ps. 咱們必需要解決這些問題,模塊與模塊之間的通信太有必要了.redux
在github上single-spa-portal-example,給出來一解決方案.bootstrap
基於Redux實現前端微服務的消息總線(不會影響在編寫代碼的時候使用其餘的狀態管理工具).api
大概思路是這樣的: 數組
每個模塊,會對外提供一個 Store.js.這個文件 裏面的內容,大體是這樣的.app
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.less
咱們須要在模塊加載器中,導出這個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);
}
複製代碼
全局派發器,主要的職責是觸發各個模塊對外的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
}
}
複製代碼
上面提到,咱們在應用註冊的時候,傳入了一個 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, ] 複製代碼
未完待續 ...