Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • Plan 计划
  • typescript-utility

  • single-spa源码

    • 开始阅读
    • app与应用管理

    • lifecycles与生命周期管理

      • 本章概要
      • 生命周期:load 和 unload
      • 生命周期:bootstrap
      • 生命周期:mount 和 unmount
        • 目录
        • toMountPromise
        • toUnmountPromise
      • 生命周期:update
    • navigation与路由管理

    • parcel组件

    • 其他

    • single-spa-react

  • qiankun源码

  • webpack

  • axios

  • solid

  • vite源码

  • jquery源码

  • snabbdom

  • am-editor

  • html2canvas

  • express

  • acorn源码

  • immutable.js源码

  • web
  • single-spa源码
  • lifecycles与生命周期管理
jonsam
2022-04-18
目录

生命周期:mount 和 unmount

本节讲解 single-spa 生命周期中 mount 和 unmount 函数的原理。在 single-spa 中 mount 阶段是在 update 阶段之前,bootstrap 阶段之后的阶段。mount 阶段的主要任务是执行 mount 阶段的钩子。unmount 则需要 unmount app 和 app.parcels。

# 目录

  • 目录
  • toMountPromise
  • toUnmountPromise

# 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

此函数核心作用是:

  • 执行应用上 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

# 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
  • unmount 过程中状态为 UNMOUNTING,成功后状态为 NOT_MOUNTED,失败后状态为 SKIP_BECAUSE_BROKEN。
  • unmount application 应用时,其下的 parcel 应用也会被 umount。
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16
生命周期:bootstrap
生命周期:update

← 生命周期:bootstrap 生命周期:update→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式