引言
该方案以redux
为核心,采用发布-订阅模式
进行封装,实现应用间通信数据上的响应式
,并在代码结构上实现模块化
,api方面仿照vuex,降低上手难度, 并可适用多框架
(如vue、react).
这次主要是针对第一版中:state任何属性发生改变, 都会触发subscribe, 当state变得庞大时,该通信模块地性能将不可避免地下降 的情况进行的改版,此次改版实现了可订阅指定state
,在灵活/性能方面有了更多了可能性
若还未看过第一版的童鞋,可以戳这里⚡qiankun微前端中的应用通信-不受框架限制的响应式数据管理
实现
设计思路
此次借鉴的react-hook的思路,通过先缓存state, 再在state发生改变时比较新旧state,从而找到具体改变的模块,最后实现订阅指定state的需求
这里用到的比较函数,是移植的react的组件状态更新时用的浅比较函数,优点是效率高,匹配度高,缺点是当state内部嵌套过深时,无法正确比较。
实际代码
这次代码我只贴有改动的地方,这样看起来更清晰,如果需要完整代码,可以看上一篇文章,或者直接去gitee上看。
此次改动仅涉及两个地方, 一个是主应用的base.ts, 一个是微应用的store
// 主应用的 @/shared/base.ts
import { Store } from 'redux';
const hasOwn = Object.prototype.hasOwnProperty;
function is(x: any, y: any) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
};
function shallowEqual(objA: any, objB: any): boolean {
if (is(objA, objB)) return true;
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
export default class BaseShared {
static pool: Store;
static actions = new Map();
static cacheState: any = null;
constructor(Pool: Store, action = new Map()) {
BaseShared.pool = Pool;
BaseShared.actions = action;
}
public init(listener: any): void {
BaseShared.cacheState = BaseShared.pool.getState();
BaseShared.pool.subscribe(() => {
const newState: any = BaseShared.pool.getState();
const stateName = BaseShared.update(BaseShared.cacheState, newState);
BaseShared.cacheState = newState;
return listener(stateName);
});
}
static update(cacheState: any, newState: any): string {
const keys = Object.keys(newState);
for(let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!shallowEqual(cacheState[key], newState[key])) return key
}
return '';
}
public dispatch(target: string, param: any = ''):any {
const res:any = BaseShared.actions.get(target)(param);
return res;
}
}
// 微应用的 @/store/index.ts 已隐藏无关代码,只显示已改动的代码
actions: {
initShared() {
shared = SharedModule.getShared();
this.dispatch('setLocale');
this.dispatch('setUserinfo');
SharedModule.subscribe([
(stateName: string) => {
this.dispatch('setLocale');
},
(stateName: string) => {
if(stateName === 'user') this.dispatch('setUserinfo');
},
]);
},
},
以上在微应用中实际使用时,可以注意到:
- 通过
SharedModule.subscribe
传入回调函数进行订阅, 可以数组形式批量传入当pool内数据有变化时(监听到redux提供的set方法执行了),会通过回调函数统一发布。 - 注册的订阅事件可以接收一个参数 stateName,该参数会返回当前发生改变的state, 例如此次demo的state有 user 和 locale, 当user里的userinfo发生改变时, 每个订阅事件都会获得
stateName
参数,告诉你具体user这个state发生了改变,这可以更好的帮助你决定更新哪些模块的状态 - 由于2实现的核心是
浅比较
,因此当stateName
为空字符串时,可以判断出是嵌套较深的state发生了改变,这在一定程度上也可以知道到底是哪个state改变了
总结&Todo
-
若还未看过第一版的童鞋,建议先看⚡qiankun微前端中的应用通信-不受框架限制的响应式数据管理,
-
在后续版本中,已实现更高度的抽象, 暴露更简洁的api请戳⚡qiankun微前端中的应用通信(三)-结构模块化,用法简单化|8月更文挑战)
-
觉得还不错的话,能否给个三键三连?(点赞、收藏、关注), 如果文章有问题的话,恳请评论回复我,每一次指导都是进步✨