一、父子间通信
1.1 基于 Props
注册子应用列表
在父应用(Vue)中,进行所有子应用服务的注册。
main.js
import { navList } from './utils/sub';import { registerApp } from './utils';// 注册、加载、启动子应用registerApp(navList);
在登记所有子应用服务的配置信息时,需将父应用的状态以及状态对应操作的action等信息添加至配置信息中。
utils/sub.js
import * as loading from '../store/loading';import * as appInfo from '../store';/*** 创建子应用的所有信息*/export const navList = [{name: 'react15', // 唯一的Key值,子应用的唯一标识entry: '//localhost:9002/', // 告诉主应用去哪个入口获取子应用的文件loading,container: '#micro-container', // 渲染容器:告知子应用在哪个容器中进行显示activeRule: '/react15', // 子应用激活规则appInfo, // 将主应用的store传递给子应用},{name: 'react16',entry: '//localhost:9003/',loading,container: '#micro-container',activeRule: '/react16',appInfo,},...]
挂载子应用
挂载子应用的时候(即mount生命周期),主应用的状态会传递给子应用。
export const mounted = async (app) => {app?.mount?.({appInfo: app.appInfo, // 将主应用的状态传给子应用entry: app.entry,});await runMainLifeCycle('mounted');};
缓存父应用状态
在子应用(react16)中的 mount 生命周期中,缓存父应用信息。
index.js
export async function mount(app) {setMain(app); // 缓存父应用render();}
utils/main.js
将父应用的全局状态以及状态操作的相关 Action 存储至子应用 main 中。
let main = null;export const setMain = (data) => {main = data;};export const getMain = () => {return main;};
更改父应用状态
在子应用的登录页面,调用父应用传递过来action,进行父应用的全局状态修改。
pages/login/index.jsx
const Login = () => {useEffect(() => {const main = getMain();if (!main.appInfo) {return;}// 登录页面隐藏头部底部main.appInfo.footerState.changeFooter(false);main.appInfo.headerState.changeHeader(false);}, []);...}
1.2 基于 CustomEvent
创建事件总线
在父应用中,基于 CustomEvent 创建一个事件总线(EventBus)。
micro/event/index.js
/*** 事件总线*/export class EVENT_BUS {// 监听事件on(name, cb) {window.addEventListener(name, (e) => cb(e.detail));}// 触发事件emit(name, data) {const event = new CustomEvent(name, {detail: data,});window.dispatchEvent(event);}}
在启动微前端框架时创建事件总线实例,并将该实例添加至全局window对象中,方便子应用访问。
micro/event/start.js
// ...import { EVENT_BUS } from './event';const event_bus = new EVENT_BUS();// 监听 init 事件event_bus.on('init', (data) => {console.log('bootstrap event:', data);});// 重要:添加事件总线全局标识window.__EVENT_BUS__ = event_bus;// ...
基于事件总线通信
在子应用(react16)中的 bootstrap 生命周期中,基于事件总线触发一个事件(init事件),在父应用中则会监听到该事件,并打印相关日志信息。
export async function bootstrap() {window.__EVENT_BUS__.emit('init', {msg: 'react16 bootstrap success',});}
二、子应用间通信
2.1 基于 CustomEvent
子应用之间的通信可以借助事件总线(EventBus)来完成,创建事件总线的过程参照上述1.2节内容。
在 vue2 子应用中,添加对 react16 事件的监听,并在监听到事件后派发一个事件,示例代码如下:
export async function mount() {window.__EVENT_BUS__.on('react16', (data) => {console.log('vue2 event:', data);window.__EVENT_BUS__.emit('vue2', {msg: 'vue2 mount success',});});render();}
在 react16 子应用的 mount 生命周期中,添加对 vue2 事件的监听,并派发一个事件,示例代码如下:
export async function mount(app) {window.__EVENT_BUS__.on('vue2', (data) => {console.log('react16 event:', data);});window.__EVENT_BUS__.emit('react16', {msg: 'react16 mount success',});setMain(app);render();}
当 vue2 子应用切换到 react16 子应用的时候,控制台就会输出如下日志。由此完成子应用之间的消息通信。
vue2 event: {msg: "react16 mount success"}react16 event: {msg: "vue2 mount success"}
注:以上机制是存在不少问题的,需注意下
- 需在unmount生命周期中,取消对事件的监听,否则每次执行mount生命周期就会不停地添加新的事件监听;
- 切换不同的子应用时,挂载事件监听的顺序不一致,可能会导致丢失一些事件的处理。如上述 react16子应用切换 vue2 子应用时,由于 vue2 子应用还未挂载监听事件,导致会丢失对
event react16事件的处理; - 若当前监听的事件非常多,不同子应用中可能会出现监听事件重名情况,针对这一问题就需做事件名的唯一性进行管理,某种程度上提升了开发难度;
2.2 基于 Props
子应用之间的通信还可以借助 Props,通信逻辑大致如下:子应用1 -> 父应用 -> 子应用2,可参考1.1节内容。
三、全局状态管理
在 2.1 节中介绍子应用基于 Custom Event 进行兄弟应用间通信是存在不少问题的,所以基于 Custom Event 进行子应用间通信并不是一个理想的技术方案,此时还可以借用全局状态管理(全局store)来进行应用间的通信。
全局状态管理也算是通信的一种,它不需我们自己去定义监听事件,也可以触发到我们所有的监听内容。
创建全局状态管理
micro/store/index.js
export const createStore = (initData = {}) =>(() => {// 利用闭包缓存初始数据let store = initData;// 管理所有的订阅者(即依赖内容)const observers = [];// 获取storeconst getStore = () => store;// 更新storeconst update = (value) => {if (value !== store) {const oldValue = store; // 缓存旧storestore = value; // 更新新的store// 通知所有订阅者store发生了变化(新值、旧值)(订阅者可能是异步,所以需加async)observers.forEach(async (item) => await item(store, oldValue));}};// 添加订阅者const subscribe = (fn) => {observers.push(fn);};return {getStore,update,subscribe,};})();
在主应用中进行子应用服务注册时,创建全局状态管理实例,并挂载在window对象上。
main/src/utils/index.js
const store = createStore();// 将实例挂载在window对象上window.store = store;store.subscribe((newValue, oldValue) => {console.log('subscribe:', newValue, oldValue);});
在子应用中就可以获取全局状态管理实例,并进行相应的状态更新。
react16/index.js
export async function mount(app) {const storeData = window.store.getStore();window.store.update({...storeData,a: 1111,});setMain(app);render();}
综上,可以看出使用全局状态有以下好处:
- 不使用任何 eventName,就可以做到事件监听;
- 同一个事件,可以添加非常多的订阅(observers);
