最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • qiankun中的数据通讯方式源码分析

    正文概述 掘金(走秀)   2021-03-09   1132

    qiankun(乾坤)

    qiankun是一个 实现微前端的一个方案(基于single-spa) 我们今天主要看下qiankun 的通讯的使用和源码的分析 可以是我们理解的更加深刻

    qiankun中通讯方式的使用

    • 用法 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。

    • 示例(来自官网) 主应用:

      import { initGlobalState, MicroAppStateActions } from 'qiankun';
      // 初始化 state
      const actions: MicroAppStateActions = initGlobalState(state);
      
      actions.onGlobalStateChange((state, prev) => {
        // state: 变更后的状态; prev 变更前的状态
        console.log(state, prev);
      });
      actions.setGlobalState(state);
      actions.offGlobalStateChange();
      

      微应用:

      // 从生命周期 mount 中获取通信方法,使用方式和 master 一致
      export function mount(props) {
      
        props.onGlobalStateChange((state, prev) => {
          // state: 变更后的状态; prev 变更前的状态
          console.log(state, prev);
        });
      
        props.setGlobalState(state);
      }
      

    qiankun中通讯的源码分析

    • 设计模式 qiankun的通讯方式 是一个典型的订阅发布的设计模式
      import { cloneDeep } from 'lodash';
      
      let globalState: Record<string, any> = {};
      const deps: Record<string, OnGlobalStateChangeCallback> = {};
    
      function initGlobalState(state: Record<string, any> = {}) {
           if (state === globalState) {
               console.warn('[qiankun] state has not changed!');
           } else {
               const prevGlobalState = cloneDeep(globalState);
               globalState = cloneDeep(state);
               emitGlobal(globalState, prevGlobalState);
           }
           return getMicroAppStateActions(`global-${+new Date()}`, true);
       }
    

    初始化 globalState 初始化是一个空的对象 deps 初始化是一个空的对象 用来存放订阅器 我们把初始化对象(state) 传给 initGlobalState 使用lodash 深克隆了我们传的参数 在这里 根据时间戳新建一个id并且 我们返回了getMicroAppStateActions函数的返回结果

      export function getMicroAppStateActions(id: string, isMaster?: boolean): MicroAppStateActions {
        return {
            onGlobalStateChange(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) {
                if (!(callback instanceof Function)) {
                    console.error('[qiankun] callback must be function!');
                    return;
                }
                if (deps[id]) {
                    console.warn(`[qiankun] '${id}' global listener already exists before this, new listener will overwrite it.`);
                }
                deps[id] = callback;
                const cloneState = cloneDeep(globalState);
                if (fireImmediately) {
                    callback(cloneState, cloneState);
                }
            },
    
            /**
             * setGlobalState 更新 store 数据
             *
             * 1. 对输入 state 的第一层属性做校验,只有初始化时声明过的第一层(bucket)属性才会被更改
             * 2. 修改 store 并触发全局监听
             *
             * @param state
             */
            setGlobalState(state: Record<string, any> = {}) {
                if (state === globalState) {
                    console.warn('[qiankun] state has not changed!');
                    return false;
                }
    
                const changeKeys: string[] = [];
                const prevGlobalState = cloneDeep(globalState);
                globalState = cloneDeep(
                    Object.keys(state).reduce((_globalState, changeKey) => {
                        if (isMaster || _globalState.hasOwnProperty(changeKey)) {
                            changeKeys.push(changeKey);
                            return Object.assign(_globalState, { [changeKey]: state[changeKey] });
                        }
                        console.warn(`[qiankun] '${changeKey}' not declared when init state!`);
                        return _globalState;
                    }, globalState),
                );
                if (changeKeys.length === 0) {
                    console.warn('[qiankun] state has not changed!');
                    return false;
                }
                emitGlobal(globalState, prevGlobalState);
                return true;
            },
    
            // 注销该应用下的依赖
            offGlobalStateChange() {
            delete deps[id];
            return true;
            },
        };
      }
    

    onGlobalStateChange 两个参数一个是callback回调函数 一个是fireImmediately 是否直接执行回调函数 我们判断了是不是 函数 和是不是已有的订阅器 我们把订阅器存放在 deps上 如果有新的订阅器 id相同的话,订阅器将会被覆盖

    setGlobalState 参数是 state

    1. 对于state 会做第一层的校验 只有是初始化的有的属性才允许被修改
     setGlobalState(state: Record<string, any> = {}) {
                if (state === globalState) {
                    console.warn('[qiankun] state has not changed!');
                    return false;
                }
    
                const changeKeys: string[] = [];
                const prevGlobalState = cloneDeep(globalState);
                globalState = cloneDeep(
                    Object.keys(state).reduce((_globalState, changeKey) => {
                        if (isMaster || _globalState.hasOwnProperty(changeKey)) {
                            changeKeys.push(changeKey);
                            return Object.assign(_globalState, { [changeKey]: state[changeKey] });
                        }
                        console.warn(`[qiankun] '${changeKey}' not declared when init state!`);
                        return _globalState;
                    }, globalState),
                );
                if (changeKeys.length === 0) {
                    console.warn('[qiankun] state has not changed!');
                    return false;
                }
                emitGlobal(globalState, prevGlobalState);
                return true;
            },
    

    我们做了一个判断它是不是主应用的 isMaster(我们在 initGlobalState 的时候穿的第二个参数) _globalState.hasOwnProperty(changeKey) 判断传入的参数 是不是初始化的时候声明的属性 如果不是控制台警告,并且不会写入 符合条件的推入到changeKeys 并且修改 _globalState 我们判断 changeKeys 的长度如果长度大于1的话我们出发 emitGlobal(globalState, prevGlobalState); 并且返回 修改成功 true

    emitGlobal

    function emitGlobal(state: Record<string, any>, prevState: Record<string, any>) {
      Object.keys(deps).forEach((id: string) => {
        if (deps[id] instanceof Function) {
          deps[id](cloneDeep(state), cloneDeep(prevState));
        }
      });
    }
    

    emitGlobal 我们根据传过来的state 和preState 我把订阅器一个个触发

    offGlobalStateChange() {
        delete deps[id];
        return true;
    },
    

    offGlobalStateChange 卸载订阅器的钩子

    在qiankun我们是如何传给子应用的 props的呢

    // 在乾坤里面唯一id
    const appInstanceId = `${appName}_${+new Date()}_${Math.floor(Math.random() * 1000)}`;
    
       // 调用 getMicroAppStateActions 返回 onGlobalStateChange setGlobalState offGlobalStateChange
      const {
        onGlobalStateChange,
        setGlobalState,
        offGlobalStateChange,
      }: Record<string, Function> = getMicroAppStateActions(appInstanceId);
    
       // 把qiankun 生成生命周期传递给 single-spa
      async props => mount({ ...props, container: containerGetter(), setGlobalState, onGlobalStateChange }),
    
       // 在 single-spa里面使用getProps获取传过来的自定义参数customProps 最后给我们子应用的钩子函数使用
      app.loadApp(getProps(app));
      const result = assign({}, customProps, {
        name,
        mountParcel: mountParcel.bind(appOrParcel),
        singleSpa,
      });
    

    结尾

    qiankun 是一个优秀的js库 他对于开发者 接入较为简单,它的js沙箱和css沙箱设计的比较巧妙 我们下次再来一起阅读它


    起源地下载网 » qiankun中的数据通讯方式源码分析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元