生命周期:mount 和 unmount
本节讲解 single-spa 生命周期中 mount 和 unmount 函数的原理。在 single-spa 中 mount 阶段是在 update 阶段之前,bootstrap 阶段之后的阶段。mount 阶段的主要任务是执行 mount 阶段的钩子。unmount 则需要 unmount app 和 app.parcels。
# 目录
# toMountPromise
toMountPromise 函数 mount 微应用。
export function toMountPromise(appOrParcel, hardFail) {
return Promise.resolve().then(() => {
// 状态必须为 NOT_MOUNTED
if (appOrParcel.status !== NOT_MOUNTED) {
return appOrParcel;
}
// 首次执行 mount 操作,dispatch before-first-mount 事件
if (!beforeFirstMountFired) {
window.dispatchEvent(new CustomEvent("single-spa:before-first-mount"));
beforeFirstMountFired = true;
}
return reasonableTime(appOrParcel, "mount")
.then(() => {
// mount 成功,将状态更新为 MOUNTED
appOrParcel.status = MOUNTED;
// 首次执行 mount 操作执行成功,dispatch first-mount 事件
if (!firstMountFired) {
window.dispatchEvent(new CustomEvent("single-spa:first-mount"));
firstMountFired = true;
}
return appOrParcel;
})
.catch((err) => {
// If we fail to mount the appOrParcel, we should attempt to unmount it before putting in SKIP_BECAUSE_BROKEN
// We temporarily put the appOrParcel into MOUNTED status so that toUnmountPromise actually attempts to unmount it
// instead of just doing a no-op.
// 如果 mount 过程发生错误,则执行 unmount
// 先将状态更新为 MOUNTED,以便 toUnmountPromise 能够执行 unmount。
appOrParcel.status = MOUNTED;
return toUnmountPromise(appOrParcel, true).then(
setSkipBecauseBroken,
setSkipBecauseBroken
);
function setSkipBecauseBroken() {
if (!hardFail) {
handleAppError(err, appOrParcel, SKIP_BECAUSE_BROKEN);
return appOrParcel;
} else {
throw transformErr(err, appOrParcel, SKIP_BECAUSE_BROKEN);
}
}
});
});
}
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
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
此函数核心作用是:
- 执行应用上 mount 阶段的钩子。执行成功后将状态更新为 MOUNTED。
- 如果 mount 失败,尝试 unmount 应用。
- dispatch before-first-mount 和 first-mount 事件。这些自定义事件,在源码内部并没有使用,是暴露给外部使用的。
关于这一点可以参考下面这个测试用例:
describe(`single-spa:first-mount events`, () => {
it(`fires first-mount exactly once when the first app is mounted`, () => {
singleSpa.registerApplication("firstMount", dummyApp, () => {
return window.location.hash.indexOf("#/firstMount") === 0;
});
singleSpa.start();
let numFirstMounts = 0,
numBeforeFirstMounts = 0;
window.addEventListener("single-spa:first-mount", () => {
numBeforeFirstMounts++;
});
window.addEventListener("single-spa:first-mount", () => {
numFirstMounts++;
});
window.location.hash = `#/firstMount`;
return singleSpa
.triggerAppChange()
.then(() => {
// Unmount
window.location.hash = `#/`;
return singleSpa.triggerAppChange();
})
.then(() => {
// Remount (shouldn't trigger an event)
window.location.hash = `#/firstMount`;
return singleSpa.triggerAppChange();
})
.then(() => {
expect(numBeforeFirstMounts).toBe(1);
expect(numFirstMounts).toBe(1);
});
});
});
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
# toUnmountPromise
toUnmountPromise 函数 unmount 微应用。
export function toUnmountPromise(appOrParcel, hardFail) {
return Promise.resolve().then(() => {
// app.status 必须是 MOUNTED
if (appOrParcel.status !== MOUNTED) {
return appOrParcel;
}
// 将 app.status 状态更新为 UNMOUNTING
appOrParcel.status = UNMOUNTING;
// 卸载应用下的子 parcel,子 parcel 依附于微应用,当微应用 unmount 时,子 parcel 均被 unmount
const unmountChildrenParcels = Object.keys(
appOrParcel.parcels
).map((parcelId) => appOrParcel.parcels[parcelId].unmountThisParcel());
let parcelError;
return Promise.all(unmountChildrenParcels)
.then(unmountAppOrParcel, (parcelError) => {
// There is a parcel unmount error
return unmountAppOrParcel().then(() => {
// Unmounting the app/parcel succeeded, but unmounting its children parcels did not
const parentError = Error(parcelError.message);
if (hardFail) {
throw transformErr(parentError, appOrParcel, SKIP_BECAUSE_BROKEN);
} else {
handleAppError(parentError, appOrParcel, SKIP_BECAUSE_BROKEN);
}
});
})
.then(() => appOrParcel);
// 将 app unmount
function unmountAppOrParcel() {
// We always try to unmount the appOrParcel, even if the children parcels failed to unmount.
return reasonableTime(appOrParcel, "unmount")
.then(() => {
// The appOrParcel needs to stay in a broken status if its children parcels fail to unmount
if (!parcelError) {
// 如果 app 和 app.parcels 均成功 unmount,更新其状态为 NOT_MOUNTED
appOrParcel.status = NOT_MOUNTED;
}
})
.catch((err) => {
if (hardFail) {
throw transformErr(err, appOrParcel, SKIP_BECAUSE_BROKEN);
} else {
handleAppError(err, appOrParcel, SKIP_BECAUSE_BROKEN);
}
});
}
});
}
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
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
- unmount 过程中状态为 UNMOUNTING,成功后状态为 NOT_MOUNTED,失败后状态为 SKIP_BECAUSE_BROKEN。
- unmount application 应用时,其下的 parcel 应用也会被 umount。
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16