当前文档对应 qiankun 3.0(RC),安装请使用 npm i qiankun@rc;2.x 文档见 v2 站点。
Skip to content

React <MicroApp> 组件(@qiankunjs/react) ​

@qiankunjs/react 提供 MicroApp 组件,用于在 React 组件树中挂载 qiankun 微应用。该组件封装了 loadMicroApp,并根据 React 组件的生命周期完成微应用的挂载、更新和卸载,无需在业务代码中自行管理实例句柄。

当 React 主应用需要在路由页面或局部面板中以组件形式使用微应用,而不希望通过 registerMicroApps 进行全局注册时,可使用该组件。

包中还提供用于主应用路由导航的 MicroAppLink,其用法见下文。

安装 ​

bash
npm install @qiankunjs/react@rc qiankun@rc

主应用必须安装 react 和 react-dom,两者的版本均需满足 >=16.9.0。

MicroAppLink 用于 registerMicroApps 路由模式下的主应用导航。主应用完成注册并调用 start 后,点击链接即可通过 single-spa 的 navigateToUrl 切换 URL,由已注册的 activeRule 决定微应用的挂载与卸载。链接本身不加载微应用。

tsx
import { MicroAppLink, type MicroAppLinkProps } from '@qiankunjs/react';
import { useRef } from 'react';

export default function Navigation() {
  const linkRef = useRef<HTMLAnchorElement>(null);
  const appLink: MicroAppLinkProps = {
    to: '/app1',
    className: 'nav-link',
    activeClassName: 'is-active',
  };

  return (
    <nav>
      <MicroAppLink {...appLink} ref={linkRef}>应用一</MicroAppLink>
      <MicroAppLink to="/app2/settings" replace>应用二设置</MicroAppLink>
    </nav>
  );
}
属性类型说明
tostring必填。 目标 URL,作为链接的 href。
replaceboolean是否替换当前历史记录。默认值为 false,导航时新增一条历史记录。
classNamestring链接的 CSS 类名。
activeClassNamestring当前地址匹配目标地址前缀时追加的 CSS 类名。默认不追加。
childrenReactNode链接内容。
refRef<HTMLAnchorElement>指向渲染出的 <a> 元素。

MicroAppLinkProps 从包入口导出。除组件自身使用的属性外,target、rel、download、aria-*、data-*、事件处理函数等原生链接属性都会传递给 <a>;href 由 to 指定。

组件先执行传入的 onClick,再判断是否接管导航。只有未被 preventDefault() 取消、未按下 Ctrl / Meta / Shift / Alt、目标为当前窗口(有效的 target 为空或为 _self,未设置时遵循页面的 <base target>)的同源 HTTP(S) 链接左键点击,才会阻止默认行为并在当前页面内导航。外链、下载链接、其他窗口目标及带修饰键的点击均保留浏览器行为。replace 为 true 时使用 history.replaceState 替换当前记录,并发送 popstate 通知路由更新。

activeClassName 将目标 URL 解析后,以它的 pathname + search + hash 为前缀匹配当前地址的对应部分。例如,to="/app1" 会匹配 /app1/settings,也会匹配 /app10;to="/" 会匹配所有路径。查询参数和哈希若包含在 to 中,也参与前缀匹配。这是字符串前缀匹配,不解析路由参数;需要精确匹配时,可由主应用自行设置 className 和 aria-current。

基础用法 ​

name 和 entry 是仅有的两个必填 prop。entry 用于指定微应用 HTML 入口的 URL。

tsx
import { MicroApp } from '@qiankunjs/react';

export default function Page() {
  return <MicroApp name="app1" entry="http://localhost:8000" />;
}

组件会渲染一个 <div> 作为挂载容器,并在组件卸载时自动卸载微应用。

name 和 entry 均为必填项

缺少 name 或 entry 时,组件仅输出错误日志 the name and entry of MicroApp is needed,不会加载微应用,也不会抛出异常。因此,必须同时提供这两个 prop。

Props ​

ts
// 组件导出的类型
export type Props = SharedProps & SharedSlots<React.ReactNode> & Record<string, unknown>;

类型末尾的 Record<string, unknown> 用于接收任意附加 prop。除下表列出的保留 prop 外,其他 prop 都会原样传递给微应用。React 绑定不提供单独的 appProps 属性,附加 prop 本身即为微应用接收的 props。

组件 props ​

属性类型默认值说明
name *string—微应用实例的名称。值发生变化时,组件会卸载当前实例并创建新实例。
entry *string—微应用的 HTML 入口地址。
settingsAppConfiguration—传递给 loadMicroApp 的加载器和沙箱配置。
lifeCyclesLifeCycles—由主应用提供的生命周期钩子,如 beforeLoad、beforeMount。
autoSetLoadingbooleanfalse是否渲染内置加载界面,并在应用挂载完成后自动结束加载状态。
autoCaptureErrorbooleanfalse是否使用内置错误边界处理加载错误。
wrapperClassNamestring—附加在包裹元素内置类名之前的自定义类名。仅在启用加载界面或错误边界时生效。
classNamestring—附加在挂载容器内置类名之前的自定义类名。
loader(loading: boolean) => ReactNode—用于自定义加载界面的渲染函数。
errorBoundary(error: Error) => ReactNode—用于自定义错误界面的渲染函数。

* 表示必填。

除组件过滤的字段外,其他 prop 会在每次渲染时进行深度比较,并传递给微应用。详见向微应用传递 props。

组件过滤的字段

组件自身消费的所有字段——name、entry、settings、lifeCycles、autoSetLoading、autoCaptureError、loader、errorBoundary、wrapperClassName 和 className——都会在调用微应用生命周期前从 props 中移除,不会到达微应用。

向微应用传递 props ​

所有未被组件过滤的 prop 都会传递给微应用的 bootstrap、mount 和 update 生命周期。

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  // 作为 props 传递给微应用
  userId={42}
  theme="dark"
  onEvent={(e) => console.log(e)}
/>

微应用可在 mount、unmount 和可选 update 的 props 参数中读取这些值;bootstrap 只接收稳定的初始化上下文:

ts
export async function mount(props) {
  console.log(props.userId, props.theme);
}

这些 prop 发生变化后,组件会使用 Lodash 的 isEqual 进行深度比较。如果微应用导出了 update 生命周期,且实例状态为 MOUNTED,组件会调用 microApp.update(props),不会重新挂载微应用。

重新挂载与更新

修改 name 会卸载当前实例并创建新实例;修改传递给微应用的其他 prop 只会尝试更新当前实例。如需完全重置实例,可修改 name,或为组件设置新的 key。组件仅以 name 作为重新挂载的依赖,因此仅修改 entry、settings 或 lifeCycles 不会创建新实例。

加载状态 ​

内部的 loading 状态初始值为 true,并在应用的 mountPromise 敲定(无论成功还是失败)后被清除。该行为与 autoSetLoading 无关:该开关只决定是否渲染内置加载界面,若把状态本身也交由它控制,自定义 loader 就会一直转下去。未提供任何加载界面时,该状态不会产生可见效果。

内置加载界面 ​

tsx
<MicroApp name="app1" entry="http://localhost:8000" autoSetLoading />

内置加载界面仅提供占位内容,渲染结果为文本 loading...。生产环境通常应通过 loader 提供自定义加载界面。

自定义加载界面 ​

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  loader={(loading) => <Spinner spinning={loading} />}
/>

自定义 loader 可独立生效,且优先级高于内置加载界面,因此无需再配合 autoSetLoading。wrapperClassName 仅在加载界面或错误界面启用时生效,因为组件只在这两种情况下渲染带定位样式的包裹元素。

错误处理 ​

默认情况下,加载、bootstrap 和 mount 阶段的错误会从异步加载流程中重新抛出。建议启用内置错误界面或提供自定义错误界面,避免产生未处理的 Promise 拒绝。

处理异步加载错误

如果既未启用 autoCaptureError,也未提供 errorBoundary,组件会重新抛出异步加载错误。React 错误边界无法捕获 Promise 回调中的错误,因此应配置组件自身的错误界面。

内置错误边界 ​

tsx
<MicroApp name="app1" entry="http://localhost:8000" autoCaptureError />

内置错误边界只渲染一个包含 error.message 的 <div>。生产环境通常应通过 errorBoundary 提供自定义错误界面。

自定义错误边界 ​

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  errorBoundary={(error) => <ErrorPanel message={error.message} />}
/>

同时启用加载状态和错误处理 ​

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  autoSetLoading
  autoCaptureError
/>

完整的错误处理方式参见处理微应用错误和 addErrorHandler / removeErrorHandler。

通过 ref 访问运行中的实例 ​

组件使用 forwardRef 转发 ref。该 ref 指向当前微应用的实例句柄,即 @qiankunjs/single-spa 的 Parcel(对应 qiankun 的 MicroApp 类型,@qiankunjs/react 以 MicroAppType 重新导出)。通过该句柄可查询实例状态,也可等待各生命周期 Promise 完成。

tsx
import { useRef } from 'react';
import { MicroApp } from '@qiankunjs/react';
import { type MicroAppType } from '@qiankunjs/react';

function Page() {
  const microAppRef = useRef<MicroAppType>(undefined);

  const logStatus = () => {
    console.log(microAppRef.current?.getStatus());
  };

  return (
    <>
      <button type="button" onClick={logStatus}>查看状态</button>
      <MicroApp
        name="app1"
        entry="http://localhost:8000"
        autoSetLoading
        ref={microAppRef}
      />
    </>
  );
}

ref 句柄 ​

该句柄即 loadMicroApp 返回的 MicroApp,在 single-spa 的 Parcel 接口上增加了 unload():

成员类型说明
getStatus()() => Status返回当前生命周期状态,取值见下文。
mount()() => Promise<null>挂载应用。
unmount()() => Promise<null>卸载应用。
unload()() => Promise<void>销毁该句柄所用的实例,释放已加载的资源。组件不会调用它。
update?(props)(props) => Promise<unknown>传入新的 props。仅当应用导出 update 生命周期时存在。
loadPromisePromise<null>表示源代码加载阶段完成的 Promise。
bootstrapPromisePromise<null>表示 bootstrap 阶段完成的 Promise。
mountPromisePromise<null>表示应用挂载阶段完成的 Promise。
unmountPromisePromise<null>表示应用卸载阶段完成的 Promise。

getStatus() 返回以下状态之一:NOT_LOADED、LOADING_SOURCE_CODE、NOT_BOOTSTRAPPED、BOOTSTRAPPING、NOT_MOUNTED、MOUNTING、MOUNTED、UPDATING、UNMOUNTING、UNLOADING、SKIP_BECAUSE_BROKEN、LOAD_ERROR。

由组件管理生命周期

ref 主要用于查询状态和等待生命周期 Promise。不应通过 ref 直接调用 mount()、unmount() 或 unload()。组件会统一处理挂载、更新和卸载;绕过组件调用这些方法可能破坏内部状态。

组件销毁时只调用 unmount(),已加载的实例会保留,再次渲染同名组件时直接复用。需要释放这些资源时,在组件销毁后调用 unloadMicroApp(name)。

传递配置 ​

加载器和沙箱相关选项通过 settings 传递,其类型为 AppConfiguration。

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  settings={{ sandbox: { styleIsolation: true } }}
/>

settings 会原样传给 loadMicroApp,组件不会替你填任何默认项。sandbox.styleIsolation 的行为参见样式隔离,sandbox 本身的行为参见 JavaScript 隔离。

生命周期钩子 ​

主应用的生命周期钩子通过 lifeCycles 传递,并在当前微应用加载、挂载和卸载的相应阶段执行。每个钩子可以是单个函数或函数数组。

tsx
<MicroApp
  name="app1"
  entry="http://localhost:8000"
  lifeCycles={{
    beforeMount: async (app) => console.log('before mount', app.name),
    afterMount: async (app) => console.log('mounted', app.name),
  }}
/>

完整的钩子列表和签名参见生命周期钩子。

样式钩子 ​

组件会添加以下两个 CSS 类名,可用于编写自定义样式:

元素CSS 类名
包裹元素(仅在加载界面或错误边界生效时渲染)qiankun-micro-app-wrapper
挂载容器(始终渲染)qiankun-micro-app-container
css
.qiankun-micro-app-wrapper {
  position: relative; /* 组件已设置内联样式,此处可补充布局规则 */
}

.qiankun-micro-app-container {
  min-height: 240px;
}

wrapperClassName 和 className 指定的值会分别添加到以上类名之前,不会替换 qiankun 提供的类名。

运行机制 ​

  • name 是实例重新挂载的标识。修改该值会卸载旧实例并创建新实例。
  • 组件会深度比较传递给微应用的 props,并通过 microApp.update 更新实例。
  • 实例状态为 MOUNTED 时,组件会在卸载前设置内部标记,避免卸载开始后继续执行更新。

相关内容 ​

基于 MIT 协议发布