Parcels是single-spa的一个高级特性。在对single-spa的注册相关api有更多了解之前,请尽量避免使用该特性。一个single-spa 的 parcel,指的是一个与框架无关的组件,由一系列功能构成,可以被应用手动挂载,无需担心由哪种框架实现。Parcels 和注册应用的api一致,不同之处在于parcel组件需要手动挂载,而不是通过activity方法被激活。

一个parcel可以大到一个应用,也可以小至一个组件,可以用任何语言实现,只要能导出正确的生命周期事件即可。在 single-spa 应用中,你的SPA可能会包括很多个注册应用,也可以包含很多parcel。通常情况下我们建议你在挂载parcel时传入应用的上下文,因为parcel可能会和应用一起卸载。

如果你只使用了一种框架,建议使用框架组件(如React、Vue、Angular组件)而不是parcel共享功能。Parcel多包裹了一层中间层,而框架组件在应用间调用时会更容易,你可以通过import语法直接在注册应用里导入一个组件。只有在涉及到跨框架的应用之间进行组件调用时,我们才需要考虑parcel的使用。 (更多细节)

快速示例

  1. // parcel 的实现
  2. const parcelConfig = {
  3. bootstrap() {
  4. // 初始化
  5. return Promise.resolve()
  6. },
  7. mount() {
  8. // 使用某个框架来创建和初始化dom
  9. return Promise.resolve()
  10. },
  11. unmount() {
  12. // 使用某个框架卸载dom,做其他的清理工作
  13. return Promise.resolve()
  14. }
  15. }
  16. // 如何挂载parcel
  17. const domElement = document.getElementById('place-in-dom-to-mount-parcel')
  18. const parcelProps = {domElement, customProp1: 'foo'}
  19. const parcel = singleSpa.mountRootParcel(parcelConfig, parcelProps)
  20. // parcel 被挂载,在mountPromise中结束挂载
  21. parcel.mountPromise.then(() => {
  22. console.log('finished mounting parcel!')
  23. // 如果我们想重新渲染parcel,可以调用update生命周期方法,其返回值是一个 promise
  24. parcelProps.customProp1 = 'bar'
  25. return parcel.update(parcelProps)
  26. })
  27. .then(() => {
  28. // 在此处调用unmount生命周期方法来卸载parcel. 返回promise
  29. return parcel.unmount()
  30. })

Pacel 配置

一个parcel只是一个由3到4个方法组成的对象。当挂载一个parcel时,你可以直接提供挂载对象,也可以提供loading方法来异步下载parcel对象。 parcel对象上的每个方法都是一个生命周期函数,返回值是promise。Parcels有3个必填生命周期函数(bootstrap, mount 和 unmount)和1个可选生命周期函数(update)。 强烈建议通过生命周期helper方法来当实现一个parcel。

一个React parcel示例如下:

  1. // myParcel.js
  2. import React from 'react'
  3. import ReactDom from 'react-dom'
  4. import singleSpaReact from 'single-spa-react'
  5. import MyParcelComponent from './my-parcel-component.component.js'
  6. export const MyParcel = singleSpaReact({
  7. React,
  8. ReactDom,
  9. rootComponent: MyParcelComponent
  10. })
  11. // 在这个示例中,singleSpaReact 处理input并生成了一个含有生命周期函数的parcel

需要使用上面例子生成的parcel,你只需引用由single-spa-react提供的Parcel组件。

  1. // mycomponent.js
  2. import Parcel from 'single-spa-react/parcel'
  3. import { MyParcel } from './myparcel.js'
  4. export class myComponent extends React.Component {
  5. render () {
  6. return (
  7. <Parcel
  8. config={MyParcel}
  9. { /* optional props */ }
  10. { /* and any extra props you want here */ }
  11. />
  12. )
  13. }
  14. }

注意在某些情况下,可选属性也可能会要求必填。(查看更多示例)

Parcel 生命周期

可以先查看 应用生命周期 来了解single-spa的生命周期方法。

初始化(Bootstrap)

这个生命周期函数只在parcel第一次挂载前调用一次。

  1. function bootstrap(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // 在这里做初始化相关工作
  6. console.log('bootstrapped!')
  7. });
  8. }

挂载(Mount)

mountParcel方法被调用且parcel未挂载时触发,一般会创建DOM元素、初始化事件监听等,从而为用户提供展示内容。

  1. function mount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // 在这里通知框架(如React等)渲染DOM
  6. console.log('mounted!')
  7. });
  8. }

Unmount

卸载(Unmount)

这个生命周期函数被调用的时机是parcel已经被挂载,且满足下列某个条件:

  • unmount()被调用
  • 父parcel或者应用被卸载

当被调用时,这个方法会清除DOM元素、DOM事件监听,清理内存泄漏、全局变量、事件订阅等在挂载parcel时创建的内容。

  1. function unmount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // 在这里通过框架语言停止渲染和移除dom
  6. console.log('unmounted!');
  7. });
  8. }

Update (optional)

更新(Update)(可选)

当调用parcel.update()会触发更新生命周期函数。该生命周期函数是可选的,parcel使用者需要在调用该方法之前确认其已经实现。

使用示例

模态框

App1 处理和联系人相关的所有逻辑(高内聚),但App2中需要新建一个联系人。 我们有以下方法在应用1和应用2中共享功能:

  • 如果两个应用使用同一个框架,可以 export/import组件实现
  • 重新实现一份创建联系人的逻辑(逻辑分散,不再内聚)
  • 使用single-spa parcels

App1导出一个parcel,包括创建联系人的功能。这样就可以在不丢失应用高内聚特性的基础上,在跨框架的应用间共享组件行为。 App1可以将moadel导出作为parcel,App2导入该parcel并使用。在下面的例子中,一个主要的好处在于从App1导出的parcel/modal也将会被卸载,而无需卸载/加载App1。

  1. // App1
  2. export const AddContactParcel = {
  3. bootstrap: bootstrapFn,
  4. mount: mountFn,
  5. unmount: unmountFn,
  6. }
  7. // App2
  8. // 获取parcel,该例子使用systemJS和React
  9. componentDidMount() {
  10. SystemJS.import('App1').then(App1 => {
  11. const domElement = document.body
  12. App2MountProps.mountParcel(App1.AddContactParcel, {domElement})
  13. })
  14. }

mountRootParcelmountParcel

single-spa 对外暴露了两套parcels相关接口。二者的区别主要在于调用者和调用接口的方式。

mountRootParcel mountParcel
上下文 singleSpa application
卸载条件 手动卸载 手动卸载 + 应用被卸载时
api 位置 singleSpa 命名导出 生命周期属性中提供

我应该使用哪个

通常我们建议使用mountParcelAPI。mountParcel允许你将parcel在应用里当做一个普通组件处理,不需要考虑parcel由哪个框架实现,也不需要强制调用unmount()方法卸载parcel

如何获取mountParcel API ?

为了能够绑定在应用的上下文中,mountParcel会作为生命周期属性进行传入。你需要在自己的应用中存储和管理其方法。

mountParcel API例子:

  1. // App1
  2. let mountParcel
  3. export const bootstrap = [
  4. (props) => {
  5. mountParcel = props.mountParcel
  6. return Promise.resolve()
  7. },
  8. // 其他更多boostrap
  9. ]

注意:一些类库(如React)支持在框架里存储和管理parcel。在这些情况下我们不需要写helper方法来存储和管理mountParcel方法。