Appearance
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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