single-spa 应用与普通的单页面是一样的,只不过它没有HTML页面。在一个single-spa中,你的SPA包含许多被注册的应用,而各个应用可以使用不同的框架。被注册的这些应用维护自己的客户端路由,使用自己需要的框架或者类库。应用只要通过挂载,便可渲染自己的html页面,并自由实现功能。“挂载”(mounted)的概念指的是被注册的应用内容是否已展示在DOM上。我们可通过应用的activity function来判断其是否已被挂载。应用在未挂载之前,会一直保持休眠状态。

创建并注册一个应用程序

要添加一个应用,首先需要注册该应用。一旦应用被注册后,必须在其入口文件(entry point)实现下面提到的各个生命周期函数。

注册应用的生命周期

在一个 single-spa 页面,注册的应用会经过下载(loaded)、初始化(initialized)、被挂载(mounted)、卸载(unmounted)和unloaded(被移除)等过程。single-spa会通过“生命周期”为这些过程提供钩子函数。

生命周期函数是 single-spa 在注册的应用上调用的一系列函数,single-spa 会在各应用的主文件中,查找对应的函数名并进行调用。

注:

  • bootstrap, mount, and unmount的实现是必须的,unload则是可选的
  • 生命周期函数必须有返回值,可以是Promise或者async函数
  • 如果导出的是函数数组而不是单个函数,这些函数会被依次调用,对于promise函数,会等到resolve之后再调用下一个函数
  • 如果 single-spa 未启动,各个应用会被下载,但不会被初始化、挂载或卸载。

single-spa 生态中有各个主流框架对于生命周期函数的实现,这些文档有助于理解这些helper执行的操作,也有助于你自己实现生命周期函数。

生命周期参数

生命周期函数使用”props” 传参,这个对象包含single-spa相关信息和其他的自定义属性。

  1. function bootstrap(props) {
  2. const {
  3. name, // 应用名称
  4. singleSpa, // singleSpa实例
  5. mountParcel, // 手动挂载的函数
  6. customProps // 自定义属性
  7. } = props; // Props 会传给每个生命周期函数
  8. return Promise.resolve();
  9. }

内置参数

每个生命周期函数的入参都会保证有如下参数:

  • name: 注册到 single-spa 的应用名称
  • singleSpa: 对singleSpa 实例的引用, 方便各应用和类库调用singleSpa提供的API时不再导入它。 可以解决有多个webpack配置文件构建时无法保证只引用一个singleSpa实例的问题。
  • mountParcel: mountParcel 函数.

自定义参数

除single-spa提供的内置参数外,还可以指定自定义参数,在调用各个生命周期函数时传入。指定方法是在调用registerApplication时,传入第4个参数。

root.application.js

  1. singleSpa.registerApplication(
  2. 'app1',
  3. () => {},
  4. () => {},
  5. { authToken: "d83jD63UdZ6RS6f70D0" }
  6. );

app1.js

  1. export function mount(props) {
  2. console.log(props.customProps.authToken); // 可以在 app1 中获取到authToken参数
  3. return reactLifecycles.mount(props);
  4. }

可能使用到的场景:

  • 各个应用共享一个公共的 access token
  • 下发初始化信息,如渲染目标
  • 传递对事件总线(event bus)的引用,方便各应用之间进行通信

注意如果没有提供自定义参数,则props.customProps默认会返回一个空对象。

生命周期帮助类

有一些帮助类库会对针对主流框架的生命周期函数进行实现以方便使用。具体可参见生态页面

下载(load)

注册的应用会被懒加载,这指的是该应用的代码会从服务器端下载并执行。注册的应用在activity function 第一次返回真值(truthy value)时,下载动作会发生。在下载过程中,建议尽可能执行少的操作,可以在bootstrap生命周期之后再执行各项操作。若确实有在下载时需要执行的操作,可将代码放入子应用入口文件中,但要放在各导出函数的外部。例如:

  1. console.log("The registered application has been loaded!");
  2. export async function bootstrap(props) {...}
  3. export async function mount(props) {...}
  4. export async function unmount(props) {...}

初始化

这个生命周期函数会在应用第一次挂载前执行一次

  1. export function bootstrap(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // One-time initialization code goes here
  6. console.log('bootstrapped!')
  7. });
  8. }

挂载

每当应用的activity function返回真值,但该应用处于未挂载状态时,挂载的生命周期函数就会被调用。调用时,函数会根据URL来确定当前被激活的路由,创建DOM元素、监听DOM事件等以向用户呈现渲染的内容。任何子路由的改变(如hashchangepopstate等)不会再次触发mount,需要各应用自行处理。

  1. export function mount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // Do framework UI rendering here
  6. console.log('mounted!')
  7. });
  8. }

卸载

每当应用的activity function返回假值,但该应用已挂载时,卸载的生命周期函数就会被调用。卸载函数被调用时,会清理在挂载应用时被创建的DOM元素、事件监听、内存、全局变量和消息订阅等。

  1. export function unmount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // Do framework UI unrendering here
  6. console.log('unmounted!');
  7. });
  8. }

移除

“移除”生命周期函数的实现是可选的,它只有在unloadApplication被调用时才会触发。如果一个已注册的应用没有实现这个生命周期函数,则假设这个应用无需被移除。

移除的目的是各应用在移除之前执行部分逻辑,一旦应用被移除,它的状态将会变成NOT_LOADED,下次激活时会被重新初始化。

移除函数的设计动机是对所有注册的应用实现“热下载”,不过在其他场景中也非常有用,比如想要重新初始化一个应用,且在重新初始化之前执行一些逻辑操作时。

  1. export function unload(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // Hot-reloading implementation goes here
  6. console.log('unloaded!');
  7. });
  8. }

超时

默认情况下,所有注册的应用遵循全局超时配置,但对于每个应用,也可以通过在主入口文件导出一个timeouts对象来重新定义超时时间。如:

app-1.main-entry.js

  1. export function bootstrap(props) {...}
  2. export function mount(props) {...}
  3. export function unmount(props) {...}
  4. export const timeouts = {
  5. bootstrap: {
  6. millis: 5000,
  7. dieOnTimeout: true,
  8. warningMillis: 2500,
  9. },
  10. mount: {
  11. millis: 5000,
  12. dieOnTimeout: false,
  13. warningMillis: 2500,
  14. },
  15. unmount: {
  16. millis: 5000,
  17. dieOnTimeout: true,
  18. warningMillis: 2500,
  19. },
  20. unload: {
  21. millis: 5000,
  22. dieOnTimeout: true,
  23. warningMillis: 2500,
  24. },
  25. };

注意millis指的是最终控制台输出警告的毫秒数,warningMillis指的是将警告打印到控制台(间隔)的毫秒数。

切换应用时过渡

如果你想为应用在挂载和卸载时加一些过渡效果(动画效果等),则需要将其和bootstrap, mount, 和 unmount等生命周期函数关联。这个single-spa 过渡仓库是个小demo,展示了生命周期之间切换时如何过渡。

对于已经挂载的应用,各个页面之间的过渡效果可由应用本身自行处理,如基于React创建的项目可使用using react-transition-group实现过渡效果。