- 前端
- 微前端
【免费下载链接】qiankun
📦 🚀 Blazing fast, simple and complete solution for micro frontends.
@qiankunjs/react是 qiankun 官方提供的 React 生态绑定包,它把loadMicroApp封装成一个声明式的<MicroApp>组件,让宿主应用可以在 React 组件树中像使用普通组件一样挂载、更新和卸载微应用,并额外提供了用于路由导航的<MicroAppLink>。读完本文,你将掌握这两个组件的全部用法:安装、Props 传递、加载态与错误边界、通过 ref 获取运行实例、settings与lifeCycles配置,以及它们底层如何与 qiankun 的 Parcel 生命周期协同工作。
安装与环境要求
npm install @qiankunjs/react@rc qiankun@rc@qiankunjs/react的 peer dependencies 为qiankun(^3.0.0-rc.15)、react与react-dom(均要求>=16.9.0)。从 package.json 可以看到,包本身依赖@qiankunjs/ui-shared(承载 React/Vue 两个绑定共用的挂载、更新、卸载逻辑)和lodash(用于 props 的深比较),同时以 workspace 方式将qiankun作为开发依赖,确保绑定与核心运行时版本一致。构建产物同时提供 ESM(dist/esm)与 CJS(dist/cjs)入口,并声明sideEffects: false,便于打包器做 tree-shaking。
组件定位:声明式挂载,而非全局注册
<MicroApp>适合这样的场景:宿主本身是 React SPA,你希望在某个路由页面、某个面板或弹层中把微应用当作一个普通组件"放"进去,而不是通过registerMicroApps将其注册为跟随 URL 自动激活的全局应用。二者都是官方推荐的挂载方式,区别在于:registerMicroApps+start由 URL 驱动激活规则(activeRule),而<MicroApp>由宿主组件自身的生命周期驱动。若你需要同时运行多个微应用实例,可参考同时运行多个微应用实例。
用<MicroAppLink>做宿主路由导航
<MicroAppLink>是为通过registerMicroApps注册的微应用提供的宿主导航链接:宿主完成注册并调用start之后,点击链接会经由 single-spa 的navigateToUrl改变 URL,由注册的activeRule决定哪些微应用挂载或卸载。链接本身不会加载任何微应用。
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}>App one</MicroAppLink> <MicroAppLink to="/app2/settings" replace>App two settings</MicroAppLink> </nav> ); }Props 一览
| Prop | 类型 | 说明 |
|---|---|---|
to | string | 必填。目标 URL,渲染为链接的href。 |
replace | boolean | 是否替换当前历史记录条目。默认false,导航时新增一条历史记录。 |
className | string | 链接的 CSS 类。 |
activeClassName | string | 当当前 URL 匹配目标 URL 前缀时追加的 CSS 类。默认不追加任何类。 |
children | ReactNode | 链接内容。 |
ref | Ref<HTMLAnchorElement> | 指向渲染出的<a>元素。 |
MicroAppLinkProps从包入口导出。除了组件自身消费的属性外,原生<a>属性(target、rel、download、aria-*、data-*、事件处理器等)都会被透传到<a>元素上,to提供href。
点击拦截规则
组件会先调用你传入的onClick,随后仅在满足全部条件时才拦截导航:
- 事件未被
preventDefault()取消; - 是左键点击(
button === 0); - 未按下 Ctrl / Meta / Shift / Alt 修饰键;
- 实际生效的
target为空或_self(省略target时遵循页面<base target>的设置); - 链接为同源(origin 一致)的 HTTP(S) 链接,且没有
download属性。
以上任一条件不满足(外部链接、下载链接、新标签页、修饰键点击),都保留浏览器原生行为。满足条件时,若设置replace,组件使用history.replaceState并派发popstate通知路由监听器;否则走navigateToUrl。这一判定逻辑实现在 shared/link.ts 的navigateMicroAppLink中,replaceUrl还做了巧妙的处理:先用事件监听器对象探测 single-spa 是否已同步处理本次replaceState,若没有收到通知再手动补发popstate,避免重复触发路由事件。
高亮匹配规则
activeClassName的匹配是字符串前缀比较,不解析路由参数:组件解析目标 URL,把pathname + search + hash与当前 URL 的对应字符串做前缀比较(实现见isMicroAppLinkActive,shared/link.ts)。因此:
to="/app1"同时匹配/app1/settings和/app10;to="/"匹配所有路径;to中的查询参数与 hash 参与同样的前缀比较。
若需要精确匹配,宿主可自行设置className和aria-current。组件通过订阅popstate、hashchange、single-spa:routing-event三个事件(见subscribeToMicroAppLinkLocation)来保持高亮状态与浏览器导航、single-spa 路由重排同步,并在订阅前后各读取一次window.location.href,以弥合"渲染到监听器安装之间"的导航窗口。
基本用法
<MicroApp>唯一必填的 props 是name和entry,其中entry是微应用 HTML 入口的 URL:
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并什么都不做——不会抛异常。请务必同时传入两者。(该日志来自 shared/index.ts 中mountMicroApp的守卫分支。)
Props 总览与保留字段
组件的 Props 类型定义如下(见 MicroApp.tsx):
import { type MicroApp } from 'qiankun'; // 导出组件类型 type Props = SharedProps & SharedSlots<React.ReactNode> & Record<string, unknown>;末尾的Record<string, unknown>是有意为之:任何不属于保留 props 的属性都会被原样转发给微应用,成为它的 props。这里没有单独的appProps——额外 props 本身就是微应用的 props(这与 Vue 版本通过专门的appProps对象传递有所区别,详见 Vue 版<MicroApp>组件)。
保留 props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
name* | string | — | 该微应用实例的名称。更改它会卸载当前实例并创建新实例。 |
entry* | string | — | 微应用的 HTML 入口 URL。 |
settings | AppConfiguration | — | 透传给loadMicroApp的加载器 / 沙箱配置。 |
lifeCycles | LifeCycles | — | 宿主为该实例提供的生命周期钩子,如beforeLoad、beforeMount。 |
autoSetLoading | boolean | false | 渲染内置加载指示器,并在应用挂载完成后自动清除。 |
autoCaptureError | boolean | false | 渲染内置错误边界,而非把加载错误向外抛出。 |
wrapperClassName | string | — | 追加到包装元素上的类。仅在加载器或错误边界激活时生效。 |
className | string | — | 追加到挂载容器元素上的类。 |
loader | (loading: boolean) => ReactNode | — | 自定义加载 UI 的 render-prop 插槽。 |
errorBoundary | (error: Error) => ReactNode | — | 自定义错误 UI 的 render-prop 插槽。 |
*= 必填。
ℹ️保留字段不会被转发组件消费的所有属性——
name、entry、settings、lifeCycles、autoSetLoading、autoCaptureError、loader、errorBoundary、wrapperClassName、className——会被组件剥除,不会到达微应用。这一剥除逻辑在 shared/index.ts 的componentOwnedProps列表与omitSharedProps中实现,注释特别说明了为什么要剥除渲染插槽:内联loader每次宿主渲染都是新函数,若泄漏给微应用会破坏 props 的深比较。
向微应用传递 props
任何非保留 prop 都会被转发给微应用,并送达其bootstrap/mount/update生命周期:
<MicroApp name="app1" entry="http://localhost:8000" // 以下都会被转发给微应用作为 props userId={42} theme="dark" onEvent={(e) => console.log(e)} />在微应用内部,这些值出现在每个生命周期的props上:
export async function mount(props) { console.log(props.userId, props.theme); }当这些 props 变化时,组件用 lodash 的isEqual做深比较(见 MicroApp.tsx 的useDeepCompare),若发生变化则对运行中的应用调用microApp.update(props)——不会重新挂载。更新只在应用状态为MOUNTED时真正执行。
💡重挂载 vs 原地更新更改
name会卸载当前实例并创建新实例。更改其他转发 prop 只尝试原地update。单独更改entry、settings或lifeCycles不会创建新实例。若要彻底重置,请更改name或给组件一个新的key。
更新机制的实现细节
updateMicroApp(shared/index.ts)并非简单调用microApp.update,而是维护了一条串行链:
- 首次更新以
mountPromise为起点,确保更新发生在挂载完成之后——这里只能"补上起点",不能"跳过本次更新",否则宿主传入的第一次 props 变更会被吞掉; - 后续更新通过
_updatingPromise链式串联,保证更新顺序与组件状态变更顺序一致,且后一个更新必须等待前一个完成; - 更新前检查
app.update存在、状态为MOUNTED且未被标记为_unmounting,避免更新与卸载竞态; - 开发环境下,若 200ms 内更新过于频繁会输出性能告警,并打印本次更新的 props,方便排查不必要的重渲染。
加载状态
内部 loading 标志初始为true,在应用的mountPromise落定后(无论成功还是失败)清除。这一行为与autoSetLoading无关——该标志只用于选择内置指示器,若以它为条件才清除 loading,会让自定义loader永远旋转。不提供加载插槽时,该状态没有任何可渲染的内容。
内置 loader
<MicroApp name="app1" entry="http://localhost:8000" autoSetLoading />内置 loader 只是渲染字面量文本loading...的占位符(见 MicroAppLoader.tsx)。要获得真正的 UI,请传入自己的loader。
自定义 loader
<MicroApp name="app1" entry="http://localhost:8000" loader={(loading) => <Spinner spinning={loading} />} />自定义loader独立生效,且优先于内置指示器,因此配合它时autoSetLoading是多余的。wrapperClassName只在加载或错误插槽激活时生效——因为只有那时组件才渲染带定位的包装元素。组件源码(MicroApp.tsx)先选择loader,否则在autoSetLoading开启时回退到内置MicroAppLoader;测试 MicroApp.test.tsx 专门验证了"不设置autoSetLoading时自定义 loader 也会在挂载完成后结束"这一行为。
错误处理
默认情况下,来自加载(load)、bootstrap、mount 的错误会从异步加载流程中重新抛出。请配置内置或自定义错误 UI,以避免产生未处理的 promise rejection。
⚠️务必处理异步加载错误既未设置
autoCaptureError也未设置errorBoundary时,组件会重新抛出异步加载错误。React 的 error boundary 无法捕获 promise 回调中抛出的错误,所以必须配置组件自身的错误 UI。
组件源码中的setComponentError(MicroApp.tsx)体现了这个设计:只有配置了错误边界才把错误写入 state 渲染,否则直接向外throw。mountMicroApp同时监听loadPromise、bootstrapPromise和mountPromise三个 promise 的失败(shared/index.ts),任一失败都会触发错误 UI 并结束加载态。
内置错误边界
<MicroApp name="app1" entry="http://localhost:8000" autoCaptureError />内置边界渲染一个只包含error.message的裸<div>(见 ErrorBoundary.tsx)。生产环境请传入自己的errorBoundary。
自定义错误边界
<MicroApp name="app1" entry="http://localhost:8000" errorBoundary={(error) => <ErrorPanel message={error.message} />} />加载与错误同时配置
<MicroApp name="app1" entry="http://localhost:8000" autoSetLoading autoCaptureError />测试 MicroApp.test.tsx 验证了加载失败时errorBoundary插槽能正确渲染错误消息、同时加载插槽被清除的行为。更系统的错误处理方案参见处理加载与运行时错误与addErrorHandler/removeErrorHandler。
通过 ref 获取运行实例
组件是一个forwardRef。转发的 ref 指向运行中的微应用句柄——来自@qiankunjs/single-spa的 Parcel(即qiankun中的MicroApp类型,由@qiankunjs/react以MicroAppType重新导出),因此你可以读取它的状态、等待它的生命周期 promise:
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}>Check status</button> <MicroApp name="app1" entry="http://localhost:8000" autoSetLoading ref={microAppRef} /> </> ); }MicroAppType在共享层还扩展了_unmounting、_updatingPromise、_updatingTimestamp三个内部字段(shared/index.ts),它们是组件协调"更新与卸载竞态""更新串行化"的私有状态。
ref 句柄成员
句柄是 single-spa 的 Parcel 接口:
| 成员 | 类型 | 说明 |
|---|---|---|
getStatus() | () => Status | 当前生命周期状态(见下)。 |
mount() | () => Promise<null> | 挂载应用。 |
unmount() | () => Promise<null> | 卸载应用。 |
update?(props) | (props) => Promise<unknown> | 推送新 props(仅当应用导出了update生命周期时存在)。 |
loadPromise | Promise<null> | 源码加载完成时 resolve。 |
bootstrapPromise | Promise<null> | 应用完成 bootstrap 时 resolve。 |
mountPromise | Promise<null> | 应用完成挂载时 resolve。 |
unmountPromise | Promise<null> | 应用完成卸载时 resolve。 |
getStatus()返回的状态之一:NOT_LOADED、LOADING_SOURCE_CODE、NOT_BOOTSTRAPPED、BOOTSTRAPPING、NOT_MOUNTED、MOUNTING、MOUNTED、UPDATING、UNMOUNTING、UNLOADING、SKIP_BECAUSE_BROKEN、LOAD_ERROR。
⚠️让组件管理生命周期ref 用于读取状态和等待 promise,不要自己调用它的
mount()/unmount()——组件掌管挂载 / 更新 / 卸载,并防护并发卸载与重挂载。手动调用这些方法容易破坏内部状态。
传递配置(settings)
加载器与沙箱相关的选项都通过settings(一个AppConfiguration)传入:
<MicroApp name="app1" entry="http://localhost:8000" settings={{ sandbox: { styleIsolation: true } }} />settings会被原样交给loadMicroApp(shared/index.ts 中与lifeCycles一起作为第二、第三个参数传入)——组件不会替你设置任何默认值。注意loadMicroApp的container必须是真实 DOM 元素而非选择器字符串,这一点由组件内部的容器div保证(测试 MicroApp.test.tsx 断言了组件把自己创建的容器交给了 qiankun)。sandbox.styleIsolation实际启用了什么,参见样式隔离;sandbox本身的含义参见 JS 沙箱。
生命周期钩子(lifeCycles)
宿主侧生命周期钩子通过lifeCycles传入,只作用于该组件创建的实例。每个钩子可以是单个函数或函数数组:
<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), }} />完整的钩子列表与签名参见生命周期钩子。实现上,mountMicroApp把lifeCycles原样传给loadMicroApp的第三个参数,由 qiankun 在其自身 addons 之上合并——源码注释特别记录了一个历史教训:曾用concat(undefined, hook)包装钩子导致产生[undefined, hook]数组,qiankun 会把它当作钩子调用,导致所有传入lifeCycles的应用挂载即崩溃,因此现在保持原样传递(shared/index.ts)。
样式钩子
组件始终会附加两个可供 CSS 定位的类:
| 元素 | 类名 |
|---|---|
| 包装元素(仅在加载器或错误边界激活时渲染) | qiankun-micro-app-wrapper |
| 挂载容器(始终渲染) | qiankun-micro-app-container |
.qiankun-micro-app-wrapper { position: relative; /* 内联样式已设置;可在此补充布局 */ } .qiankun-micro-app-container { min-height: 240px; }wrapperClassName和className会被前置拼接到这两个类之前(见 MicroApp.tsx),因此你的类与 qiankun 的钩子类会同时出现在元素上。
容器排序的一个隐藏细节
组件刻意把挂载容器放在包装元素的第一个子节点位置(MicroApp.tsx)。原因是:qiankun 以容器的 XPath 为键做 parcel 缓存与同容器串行化,而 XPath 计数的是同标签兄弟节点之前的元素。若 loader / error 面板条件渲染在容器之前,会在两次挂载之间改变该序号,把同一个应用静默拆成两个缓存条目。插槽渲染在容器之后,还能在不依赖 z-index 的情况下盖在应用内容之上。这一约束被测试 MicroApp.test.tsx 明确守护。
底层工作原理
- 挂载以
name为键:更改name会完整重挂载一个全新应用。组件通过useEffect依赖[name]实现(MicroApp.tsx),且挂载/卸载经由一条lifecycleRefpromise 链串行化——effect 的 cleanup 可能落在挂载仍在飞行中时(React StrictMode 在开发模式下每次挂载都会如此),卸载必须先等待那次挂载完成,否则会找不到实例而跳过卸载,导致实例一直挂载着、后继实例永远等它。测试 MicroApp.test.tsx 分别验证了 StrictMode 下只留下一个存活实例、快速切换name时事件严格按mount:a → unmount:a → mount:b → …串行执行。 - props 更新由深比较驱动:转发 props 经
useDeepCompare(lodashisEqual)比较后,变化时走microApp.update,且更新链与挂载 promise 对齐,不会在挂载完成前误发更新。 - 卸载时标记防竞态:实例状态为
MOUNTED时,组件在拆卸前先把microApp._unmounting = true,使 prop 更新不会与卸载竞争(MicroApp.tsx)。组件卸载时,unmountMicroApp先等待mountPromise再调用unmount()(shared/index.ts)。
相关资源
loadMicroApp—— 本组件封装的底层命令式 API。AppConfiguration——settings的数据结构。- 生命周期钩子 ——
lifeCycles的数据结构。 - Vue 版
<MicroApp>组件 —— Vue 版本(注意:Vue 通过专门的appProps对象传递应用 props)。 - 同时运行多个微应用实例
- 组件源码:MicroApp.tsx、MicroAppLink.tsx、共享逻辑 shared/index.ts 与 shared/link.ts。
- 前端
- 微前端
【免费下载链接】qiankun
📦 🚀 Blazing fast, simple and complete solution for micro frontends.
相关推荐
EngineerCMS:工程师的知识管理利器
EngineerCMS:工程师的知识管理利器 在数字化时代,工程师们面临着海量信息的挑战,如何高效地管理、共享和利用这些知识成为了迫切的需求。今天,我们要介绍的
前端微前端qiankun Vue 绑定实战指南:用 @qiankunjs/vue 的 MicroApp 组件声明式加载微应用
qiankun Vue 绑定实战指南:用 @qiankunjs/vue 的 MicroApp 组件声明式加载微应用 @qiankunjs/vue 是 qiank
前端微前端猫抓资源嗅探插件实战指南:从捕获到M3U8合并的完整流程
猫抓资源嗅探插件实战指南:从捕获到M3U8合并的完整流程 猫抓 cat catch 是一款开源的浏览器资源嗅探扩展,它实时监听网页发出的网络请求,把藏在页面里的
前端微前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考