⚡qiankun微前端中的应用通信(二)-可订阅指定state|8月更文挑战

1,323 阅读3分钟

引言

该方案以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');
                },
            ]);
        },
    },

以上在微应用中实际使用时,可以注意到:

  1. 通过 SharedModule.subscribe 传入回调函数进行订阅, 可以数组形式批量传入当pool内数据有变化时(监听到redux提供的set方法执行了),会通过回调函数统一发布。
  2. 注册的订阅事件可以接收一个参数 stateName,该参数会返回当前发生改变的state, 例如此次demo的state有 user 和 locale, 当user里的userinfo发生改变时, 每个订阅事件都会获得stateName参数,告诉你具体user这个state发生了改变,这可以更好的帮助你决定更新哪些模块的状态
  3. 由于2实现的核心是浅比较,因此当stateName为空字符串时,可以判断出是嵌套较深的state发生了改变,这在一定程度上也可以知道到底是哪个state改变了

总结&Todo

  1. 目前演示demo已经上传至gitee: 主应用微应用,关于本次的内容,两个仓库均请切换到v2分支。

  2. 若还未看过第一版的童鞋,建议先看⚡qiankun微前端中的应用通信-不受框架限制的响应式数据管理

  3. 在后续版本中,已实现更高度的抽象, 暴露更简洁的api请戳⚡qiankun微前端中的应用通信(三)-结构模块化,用法简单化|8月更文挑战

  4. 觉得还不错的话,能否给个三键三连?(点赞、收藏、关注), 如果文章有问题的话,恳请评论回复我,每一次指导都是进步✨