https://github.com/micro-zoe/micro-app/issues/21
在做框架的时候,没法避免的遇见父子通信和兄弟通信的问题,但是如何实现通信这是个每个框架需要解决的问题。
最好的方式应该是像普通元素一样,通过micro-app元素传递数据。但自定义元素无法支持对象类型的属性,只能传递字符串,例如 会转换为 。
所以选择通过改写原型链上setAttribute方法处理对象类型属性,对传递的数据进行拦截,加上发布订阅实现父子的通信。
// /src/index.js// 记录原生方法
const rawSetAttribute = Element.prototype.setAttribute// 重写setAttribute
Element.prototype.setAttribute = function setAttribute (key, value) {// 目标为micro-app标签且属性名称为data时进行处理if (/^micro-app/i.test(this.tagName) && key === 'data') {if (toString.call(value) === '[object Object]') {// 克隆一个新的对象const cloneValue = {}Object.getOwnPropertyNames(value).forEach((propertyKey) => {// 过滤vue框架注入的数据if (!(typeof propertyKey === 'string' && propertyKey.indexOf('__') === 0)) {cloneValue[propertyKey] = value[propertyKey]}})// 数据拦截后对子应用发送数据BaseAppData.setData(this.getAttribute('name'), cloneValue)}} else {rawSetAttribute.call(this, key, value)}
}
微前端各个应用本身是独立运行的,通信系统不应该对应用侵入太深,所以我们采用发布订阅系统。
子应用要发给其他子应用的数据先发给基座,基座进行分发。
发布订阅系统很灵活,但太过于灵活可能会导致数据传输的混乱,必须定义一套清晰的数据流。所以我们要进行数据绑定,基座应用一次只能向指定的子应用发送数据,子应用只能发送数据到基座应用,至于子应用之间的数据通信则通过基座应用进行控制,这样数据流就会变得清晰
因为元素不能传递对象,所以最终的结果还是走了发布订阅。通过修改原生的setAttribute方法,当改变属性的时候,进行获取值,然后将获取的值放入发布订阅当中。进行传递。

// /src/index.js// 记录原生方法
const rawSetAttribute = Element.prototype.setAttribute// 重写setAttribute
Element.prototype.setAttribute = function setAttribute (key, value) {// 目标为micro-app标签且属性名称为data时进行处理if (/^micro-app/i.test(this.tagName) && key === 'data') {if (toString.call(value) === '[object Object]') {// 克隆一个新的对象const cloneValue = {}Object.getOwnPropertyNames(value).forEach((propertyKey) => {// 过滤vue框架注入的数据if (!(typeof propertyKey === 'string' && propertyKey.indexOf('__') === 0)) {cloneValue[propertyKey] = value[propertyKey]}})// 发送数据BaseAppData.setData(this.getAttribute('name'), cloneValue)}} else {rawSetAttribute.call(this, key, value)}
}
发布订阅功能的代码实现
// /src/data.js// 基座应用的数据通信方法集合
export class EventCenterForBaseApp {/*** 向指定子应用发送数据* @param appName 子应用名称* @param data 对象数据*/setData (appName, data) {eventCenter.dispatch(formatEventName(appName, true), data)}/*** 清空某个应用的监听函数* @param appName 子应用名称*/clearDataListener (appName) {eventCenter.off(formatEventName(appName, false))}
}
子应用对父亲的传值,通过标签本身的属性[data],通过datachange方法来进行传值的
核心代码
const app = appInstanceMap.get(this.appName)if (app?.container) {// 子应用以自定义事件的形式发送数据const event = new CustomEvent('datachange', {detail: {data,}})app.container.dispatchEvent(event)}
// /src/data.js// 基座应用的数据通信方法集合
export class EventCenterForBaseApp {/*** 向指定子应用发送数据* @param appName 子应用名称* @param data 对象数据*/setData (appName, data) {eventCenter.dispatch(formatEventName(appName, true), data)}/*** 清空某个应用的监听函数* @param appName 子应用名称*/clearDataListener (appName) {eventCenter.off(formatEventName(appName, false))}
}// 子应用的数据通信方法集合
export class EventCenterForMicroApp {constructor (appName) {this.appName = appName}/*** 监听基座应用发送的数据* @param cb 绑定函数*/addDataListener (cb) {eventCenter.on(formatEventName(this.appName, true), cb)}/*** 解除监听函数* @param cb 绑定函数*/removeDataListener (cb) {if (typeof cb === 'function') {eventCenter.off(formatEventName(this.appName, true), cb)}}/*** 向基座应用发送数据* @param data 对象数据*/dispatch (data) {const app = appInstanceMap.get(this.appName)if (app?.container) {// 子应用以自定义事件的形式发送数据const event = new CustomEvent('datachange', {detail: {data,}})app.container.dispatchEvent(event)}}/*** 清空当前子应用绑定的所有监听函数*/clearDataListener () {eventCenter.off(formatEventName(this.appName, true))}
}