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
- 对于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沙箱设计的比较巧妙 我们下次再来一起阅读它
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!