qiankun源码分析-5.钩子函数调用

238 阅读3分钟

start

在start1中大致讲了qiankun是如何启动子应用的,我们回顾下流程:

  1. 配置预加载策略与兼容性处理
  2. 调用single-spa的start方法
  3. 触发single-spa:no-app-change事件
  4. 调用setDefaultMountApp方法,触发路由跳转
  5. 触发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钩子函数.分析到这里,应该可以打住了,其他钩子函数的调用地方,也都可以很容易找到。