Skip to content
On this page

reroute 方法

这个方法是整个 Single-SPA 中最核心的方法,当路由切换时也会执行该逻辑

目录

bash
└── src
    ├── applications
    │   ├── app.helpers.js
    │   └── app.js
    ├── navigations
    │   └── reroute.js # 重置路由
    ├── single-spa.js
    └── start.js
1
2
3
4
5
6
7
8

获取对应状态的 app

javascript
import { getAppChanges } from '../applications/apps';

export function reroute() {
    const {
        appsToLoad, // 获取要去加载的app
        appsToMount, // 获取要被挂载的
        appsToUnmount // 获取要被卸载的
    } = getAppChanges();
}
1
2
3
4
5
6
7
8
9
javascript
const apps = []; // 注册的服务

// 获取 app 改变状态
export function getAppChanges() {
  const appsToUnmount = []; // app 去卸载
  const appsToLoad = []; // app 去加载
  const appsToMount = []; // app 去挂载
  //
  apps.forEach((app) => {
    //  状态不是----运行出错
    //  当前app是否应该激活, 如果返回 true ,那么应用应该开始初始化等一系列操作
    const appShouldBeActive =
      app.status !== SKIP_BECAUSE_BROKEN && shouldBeActive(app);
    //
    switch (
      app.status // toLoad
    ) {
      case NOT_LOADED: // 没有加载过
      case LOADING_SOURCE_CODE: // 加载原代码
        if (appShouldBeActive) {
          appsToLoad.push(app);
        }
        break;
      case NOT_BOOTSTRAPPED: /// 没有启动
      case NOT_MOUNTED: // 没有挂载
        if (appShouldBeActive) {
          appsToMount.push(app);
        }
        break;
      case MOUNTED: // 挂载完毕 // toUnmount
        if (!appShouldBeActive) {
          appsToUnmount.push(app);
        }
    }
  });
  return { appsToUnmount, appsToLoad, appsToMount };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

预加载应用

当用户没有调用 start 方法时,我们默认会先进行应用的加载

javascript
  // start方法调用时候是同步,但是加载流程是异步的
  if (started) {
    // app 装载
    console.log("调用 start 方法");
    return performAppChanges(); // 通过路径来装载应用
  } else {
    // 注册应用时 需要预先加载
    // console.log("调用 registerApplication");
    return loadApps();
  }
1
2
3
4
5
6
7
8
9
10

生命周期处理

bash
└── src
    ├── applications
    │   ├── app.helpers.js
    │   └── app.js
    ├── lifecycles # 生命周期函数
    │   ├── bootstarp.js # 启动
    │   ├── load.js # 加载
    │   ├── mount.js # 挂载
    │   ├── unload.js # 移出加载
    │   └── unmount.js # 卸载
    ├── navigations
    │   ├── navigation-events.js
    │   └── reroute.js
    ├── single-spa.js
    └── start.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

toLoadPromise

用户 load 函数返回的 bootstrap、mount、unmount 可能是数组形式,我们将这些函数进行组合

javascript
import { toLoadPromise } from '../lifecycles/load';
 // 预加载应用
async function loadApps() {
    // 就是获取 bootstrap,mount和 unmount 方法放在 app 上
    let apps = await appsToLoad.map(toLoadPromise);
}
1
2
3
4
5
6
javascript

import {
  NOT_LOADED,
  LOADING_SOURCE_CODE,
  NOT_BOOTSTRAPPED,
} from "../applications/app.helpers";

function flattenFnArray(fns) {
  fns = Array.isArray(fns) ? fns : [fns];
  // 通过 Promise链式来链式调
  return (props) => {
    return fns.reduce((p, fn) => {
      return p.then(() => fn(props));
    }, Promise.resolve());
  };
  // Promise.resolve().then(() => fns(props));
}

// 重复加载
export async function toLoadPromise(app) {
  // console.log("app", app);
  if (app.loadPromise) {
    return app.loadPromise;
  }

  if (app.status !== NOT_LOADED) {
    return app;
  }

  app.status = LOADING_SOURCE_CODE; // 加载原代码
  return (app.loadPromise = Promise.resolve().then(async () => {
    let { bootstrap, mount, unmount } = await app.loadApp(app.customProps);

    app.status = NOT_BOOTSTRAPPED;
    // 我希望将多个 promise 组合在一起 compose
    app.bootstrap = flattenFnArray(bootstrap);
    app.mount = flattenFnArray(mount);
    app.unmount = flattenFnArray(unmount);
    delete app.loadPromise;
    return app;
  }));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

app 运转逻辑

路由切换时卸载不需要的应用

javascript
import {toUnmountPromise} from '../lifecycles/unmount';
import {toUnloadPromise} from '../lifecycles/unload';

// 卸载不需要的应用,挂载需要的应用
async function performAppChanges(){
    let unmountPromises = appsToUnmount.map(toUnmountPromise).map(unmountPromise=>unmountPromise.then(toUnloadPromise));
}
1
2
3
4
5
6
7

toUnmountPromise

javascript
import { UNMOUNTING, NOT_MOUNTED, MOUNTED } from "../applications/app.helpers";

export async function toUnmountPromise(app) {
  // 当前应用没有被挂在直接什么都不做了
  if (app.status != MOUNTED) {
    return app;
  }
  app.status = UNMOUNTING; // 正在卸载中
  await app.unmount(app.customProps);
  app.status = NOT_MOUNTED; // 没有挂载
  return app;
}
1
2
3
4
5
6
7
8
9
10
11
12

toUnmountPromise

javascript
import { NOT_LOADED, UNLOADING } from "../applications/app.helpers";
const appsToUnload = {};
export async function toUnloadPromise(app) {
  if (!appsToUnload[app.name]) {
    return app;
  }
  app.status = UNLOADING; // 没有加载过
  delete app.bootstrap;
  delete app.mount;
  delete app.unmount;
  app.status = NOT_LOADED; // 加载原代码
}
1
2
3
4
5
6
7
8
9
10
11
12

匹配到没有加载过的应用 (加载 => 启动 => 挂载)

loadThenMountPromises

javascript
// 去加载需要的应用
// 这个应用可能需要加载 但是路径不匹配 加载 app1的时候,这个时候,切换到了 app2
// 将需要加载的应用拿到 => 加载 => 启动 => 挂载
const loadThenMountPromises = appsToLoad.map(async (app) => {
    app = await toLoadPromise(app);
    app = await toBootstrapPromise(app);
    return toMountPromise(app);
});
1
2
3
4
5
6
7
8

这里需要注意一下,可能还有没加载完的应用这里不要进行重复加载

javascript
export async function toLoadPromise(app) {
    if(app.loadPromise){
        return app.loadPromise;
    }
    if (app.status !== NOT_LOADED) {
        return app;
    }
    app.status = LOADING_SOURCE_CODE;
    return (app.loadPromise = Promise.resolve().then(async ()=>{
        let { bootstrap, mount, unmount } = await app.loadApp(app.customProps);

        app.status = NOT_BOOTSTRAPPED;
        app.bootstrap = flattenFnArray(bootstrap);
        app.mount = flattenFnArray(mount);
        app.unmount = flattenFnArray(unmount);
        delete app.loadPromise;
        return app;
    }));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

toBootstrapPromise

javascript
import {
  BOOTSTRAPPING,
  NOT_MOUNTED,
  NOT_BOOTSTRAPPED,
} from "../applications/app.helpers";

export async function toBootstrapPromise(app) {
  if (app.status !== NOT_BOOTSTRAPPED) {
    // 没有启动
    return app;
  }
  app.status = BOOTSTRAPPING; // 启动中
  await app.bootstrap(app.customProps);
  app.status = NOT_MOUNTED; // 没有挂载
  return app;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

toMountPromise

javascript
import { MOUNTED, MOUNTING, NOT_MOUNTED } from "../applications/app.helpers.js";
export async function toMountPromise(app) {
  if (app.status !== NOT_MOUNTED) {
    // 挂载完毕
    return app;
  }
  app.status = MOUNTING; //  挂载中
  await app.mount();
  app.status = MOUNTED; // 挂载完毕
  return app;
}

1
2
3
4
5
6
7
8
9
10
11
12

已经加载过了的应用 (启动 => 挂载)

mountPromises

javascript
const mountPromises = appsToMount.map(async (app) => {
    app = await toBootstrapPromise(app);
    return toMountPromise(app);
});
await Promise.all(unmountPromises); // 等待先卸载完成
await Promise.all([...loadThenMountPromises,...mountPromises]);
1
2
3
4
5
6

总结

javascript
import { getAppChanges } from "../applications/app";
import { started } from "../start";
import { toLoadPromise } from "../lifecycles/load";
import { toUnloadPromise } from "../lifecycles/unload";
import { toUnmountPromise } from "../lifecycles/unmount";
import { toBootstrapPromise } from "../lifecycles/bootstarp";
import { toMountPromise } from "../lifecycles/mount";
import "./navigation-events";

// 核心应用处理方法
// 这个流程是用于初始化操作的,我们还需要 当路径切换时重新加载应用
// 重写路由相关的方法

export function reroute() {
  const {
    appsToLoad, // 获取要去加载的app
    appsToMount, // 获取要被挂载的
    appsToUnmount, // 获取要被卸载的
  } = getAppChanges();

  // start方法调用时候是同步,但是加载流程是异步的
  if (started) {
    // app 装载
    console.log("调用 start 方法");
    return performAppChanges(); // 通过路径来装载应用
  } else {
    // 注册应用时 需要预先加载
    // console.log("调用 registerApplication");
    return loadApps();
  }

  //预加载应用
  async function loadApps() {
    let apps = await appsToLoad.map(toLoadPromise); // 就是获取 bootstrap,mount和 unmount 方法放在 app 上
  }

  //根据路径装载应用
  async function performAppChanges() {
    // 先卸载不需要的应用
    let unmountPromises = appsToUnmount
      .map(toUnmountPromise)
      .map((unmountPromise) => unmountPromise.then(toUnloadPromise)); // 需要去卸载的 app

    // 去加载需要的应用
    // 这个应用可能需要加载 但是路径不匹配 加载 app1的时候,这个时候,切换到了 app2
    // 将需要加载的应用拿到 => 加载 => 启动 => 挂载
    const loadThenMountPromises = appsToLoad.map(async (app) => {
      app = await toLoadPromise(app);
      app = await toBootstrapPromise(app);
      return toMountPromise(app);
    });

    const mountPromises = appsToMount.map(async (app) => {
      app = await toBootstrapPromise(app);
      return toMountPromise(app);
    });

    await Promise.all(unmountPromises); // 等待先卸载完成
    await Promise.all([...loadThenMountPromises, ...mountPromises]);
  }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
沪ICP备20006251号-1