start
在start1中大致讲了qiankun是如何启动子应用的,我们回顾下流程:
- 配置预加载策略与兼容性处理
- 调用single-spa的start方法
- 触发single-spa:no-app-change事件
- 调用setDefaultMountApp方法,触发路由跳转
- 触发single-spa的urlReroute事件,开始路由拦截,并加载子应用
讲了这么多,我们发现核心逻辑都是在single-spa中,qiankun只是在single-spa的基础上做了一层封装,那么qiankun的核心价值在哪里呢?,它又做了什么?
在开发使用过程中,我们知道在子应用中可以定义钩子函数,qiankun会在合适的时机调用这些钩子函数,例如我们的mount钩子函数,在钩子函数中,我们可以使用render方法,渲染我们的子应用,那么这些钩子函数是什么时候调用的,带着这个疑问,我们展开分析下钩子。
子应用的钩子是什么时候被调用的?
我们先来看下子应用的钩子是如何注册的,我们子应用的入口文件暴露了钩子函数:
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
在我们加载子应用的时候,首先做的是资源的获取(fetch),然后才是load,那么在load的时候,有一段逻辑是这样的:
// single-spa
const loadPromise = app.loadApp(getProps(app))
loadPromise.then((val) => {
appOpts = val;
app.bootstrap = flattenFnArray(appOpts, "bootstrap");
app.mount = flattenFnArray(appOpts, "mount");
app.unmount = flattenFnArray(appOpts, "unmount");
app.unload = flattenFnArray(appOpts, "unload");
})
从上面这段代码中,可以看到,single-spa在加载子应用的时候,会将子应用的钩子函数注册到app上,那么我们继续看下loadApp的实现:
// qiankun
export async function loadApp<T extends object = {}>(app: RegistrableApp<T>, configuration?: LoadableApp<T>): Promise<RegistrableApp<T>> {
// ...
const { entry, getTemplate = getDefaultTplWrapper(app), ...importEntryOpts } = configuration || {};
// ...
const { template, execScripts, assetPublicPath, getExternalScripts } = await importEntry(entry, importEntryOpts);
// trigger external scripts loading to make sure all assets are ready before execScripts calling
// 获取子应用的外部脚本
await getExternalScripts();
// get the lifecycle hooks from module exports
const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox, {
scopedGlobalVariables: speedySandbox ? cachedGlobals : [],
});
const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
scriptExports,
appName,
global,
sandboxContainer?.instance?.latestSetProp,
);
// ...
return {
// ...
mount: [
// ...
async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }),
// ...
],
// ...
};
}
可以看到,通过importEntry方法获取子应用的外部脚本,然后通过execScripts方法执行脚本,脚本暴露了子应用钩子函数(至于为什么会暴露,还记得qiankun对子应用的改造吗,在webpack导出的配置有相关修改),最终在我们的return的结果上添加了mount属性,mount是个数组,
数组的其中一项就是我们的子应用的mount钩子函数,最终这个mount数组也会挂载到app上.
那么single-spa是如何调用子应用的mount钩子函数的?在single-spa挂载阶段有这样一段代码:
// single-spa
function toMountPromise () {
reasonableTime(appOrParcel, "mount")
}
export function reasonableTime(appOrParcel, lifecycle) {
return new Promise((resolve, reject) => {
appOrParcel[lifecycle](getProps(appOrParcel))
.then((val) => {
finished = true;
resolve(val);
})
});
}
挂载阶段会调用reasonableTime方法,该方法会调用子应用的mount钩子函数.分析到这里,应该可以打住了,其他钩子函数的调用地方,也都可以很容易找到。