在 Vue 主应用中声明式接入微应用:qiankun<MicroApp>组件与MicroAppLink完整指南
【免费下载链接】qiankun📦 🚀 Blazing fast, simple and complete solution for micro frontends.项目地址: https://gitcode.com/gh_mirrors/qi/qiankun
@qiankunjs/vue是 qiankun 官方提供的 Vue 绑定包,通过<MicroApp>组件以声明式方式加载、挂载、更新与卸载微应用,将微应用实例的生命周期与 Vue 组件的生命周期绑定在一起;同时提供<MicroAppLink>用于 Vue 3 主应用的路由导航。本文以 docs/zh-CN/ecosystem/vue.md 为骨架,结合仓库内 Vue 绑定源码 与 共享逻辑,完整讲解安装方式、全部 Props 与插槽、加载/错误界面、props 深度更新、实例句柄与 CSS 钩子,帮助你用最少的样板代码把微应用嵌入 Vue 主应用,并理解组件底层与loadMicroApp的封装关系。
安装
npm install @qiankunjs/vue@rc qiankun@rc主应用必须安装vue,版本范围为^2.0.0 || >=3.0.0。组件基于vue-demi构建,同一份构建产物同时支持 Vue 2 和 Vue 3,因此Vue 2 项目还需要额外安装@vue/composition-api(组件通过vue-demi使用组合式 API)。
从 packages/ui-bindings/vue/package.json 可以看到完整的依赖声明:
vue-demi(^0.14.10)作为运行时依赖,负责 Vue 2/3 运行时切换;vue与qiankun(^3.0.0-rc.15)作为 peer dependency;@vue/composition-api(^1.7.2)标记为可选 peer dependency,仅在 Vue 2 下需要。
包同时提供 CJS 与 ESM 两种构建产物(main指向dist/cjs、module指向dist/esm),类型声明随dist/esm/index.d.ts一起发布。
::: tip 使用前提<MicroApp>组件内部直接调用loadMicroApp,单独使用时无需调用registerMicroApps或start。如果同一主应用里还使用基于路由的注册方式,则仍需调用start。挂载和更新操作与 single-spa 生命周期的对应关系参见微应用生命周期与 props。 :::
路由导航:MicroAppLink
MicroAppLink用于 Vue 3 主应用的registerMicroApps路由模式。主应用完成注册并调用start后,点击链接即可通过 single-spa 的navigateToUrl切换 URL,由已注册的activeRule决定微应用的挂载与卸载。链接本身不加载微应用,它只是路由导航的声明式入口。
<script setup lang="ts"> import { MicroAppLink, type MicroAppLinkProps } from '@qiankunjs/vue'; const appLink: MicroAppLinkProps = { to: '/app1', className: 'nav-link', activeClassName: 'is-active', }; </script> <template> <nav> <MicroAppLink v-bind="appLink">应用一</MicroAppLink> <MicroAppLink to="/app2/settings" replace>应用二设置</MicroAppLink> </nav> </template>MicroAppLink 属性
| 属性 | 类型 | 说明 |
|---|---|---|
to | string | 必填。目标 URL,作为链接的href。 |
replace | boolean | 是否替换当前历史记录。默认值为false,导航时新增一条历史记录。 |
className | string | 链接的 CSS 类名,模板中也可写为class-name。 |
activeClassName | string | 当前地址匹配目标地址前缀时追加的 CSS 类名,模板中也可写为active-class-name。默认不追加。 |
默认插槽提供链接内容。MicroAppLinkProps从包入口导出(见 packages/ui-bindings/vue/src/index.ts)。除组件自身使用的属性外,class、target、rel、download、aria-*、data-*和事件监听器等原生链接属性都会传递给<a>;href由to指定。
点击拦截与导航规则
从源码 packages/ui-bindings/vue/src/MicroAppLink.ts 可以看到,组件的onClick会先执行传入的@click监听器,再判断是否接管导航。其核心判断逻辑位于共享模块 packages/ui-bindings/shared/src/link.ts 的navigateMicroAppLink函数,只有同时满足以下条件的左键点击才会阻止默认行为并在当前页面内导航:
- 事件未被
preventDefault()取消; - 未按下 Ctrl / Meta / Shift / Alt 修饰键;
- 目标为当前窗口(有效的
target为空或为_self,未设置时遵循页面的<base target>设置); - 链接为同源的 HTTP(S) 链接,且不带
download属性。
外链、下载链接、其他窗口目标及带修饰键的点击均保留浏览器行为。可通过@click.prevent取消组件导航。replace为true时,组件使用history.replaceState替换当前记录,并通过popstate事件通知路由监听器(源码中用一个临时的事件监听对象探测 single-spa 是否已同步处理该通知,避免重复派发popstate)。
activeClassName 的前缀匹配
activeClassName的匹配逻辑在isMicroAppLinkActive函数中实现:将目标 URL 解析后,以它的pathname + search + hash为前缀匹配当前地址的对应部分。例如:
to="/app1"会匹配/app1/settings,也会匹配/app10;to="/"会匹配所有路径;- 查询参数和哈希若包含在
to中,也参与前缀匹配。
这是字符串前缀匹配,不解析路由参数;需要精确匹配时,可由主应用自行设置className和aria-current。此外,组件在挂载时会通过subscribeToMicroAppLinkLocation订阅popstate、hashchange和single-spa:routing-event事件,确保浏览器前进后退或 single-spa 重路由后activeClassName依然保持最新。
基本用法
<script setup> import { MicroApp } from '@qiankunjs/vue'; </script> <template> <micro-app name="app1" entry="http://localhost:8000" /> </template>name和entry是仅有的两个必填 prop。name用于标识当前实例,entry用于指定微应用的 HTML 入口 URL。缺少其中任意一项时,组件仅输出错误日志(共享逻辑mountMicroApp中的'the name and entry of MicroApp is needed'),不会加载微应用,也不会抛出异常。
组件会渲染一个class为qiankun-micro-app-container的<div>,并将微应用内容流式写入该容器。只有启用加载状态或错误边界时,组件才会额外渲染一层包裹元素,详见下文加载与错误界面。
从 MicroApp.ts 的render函数可以确认一个实现细节:挂载容器<div>会先于插槽渲染。这是有意的设计——qiankun 以容器的 XPath 作为 Parcel 缓存键,如果加载指示器条件渲染导致容器在 DOM 树中的兄弟序号变化,会把同一个微应用拆散到两个缓存键下。
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | — | 必填。微应用实例的名称;值发生变化时会重新挂载。 |
entry | string | — | 必填。微应用的 HTML 入口 URL。 |
settings | AppConfiguration | { sandbox: true } | 传递给loadMicroApp的加载器和沙箱配置。参见 AppConfiguration。 |
lifeCycles | LifeCycles | undefined | 由主应用提供的生命周期钩子,包括beforeLoad、beforeMount、afterMount、beforeUnmount和afterUnmount。每项可传入函数或函数数组。参见生命周期钩子。 |
autoSetLoading | boolean | false | 微应用加载期间渲染内置的加载指示器。 |
autoCaptureError | boolean | false | 加载失败时渲染内置的错误边界。 |
wrapperClassName | string | undefined | 包裹元素上的额外 CSS 类名。仅在启用加载状态或错误边界时生效。 |
className | string | undefined | 挂载容器元素上的额外 CSS 类名。 |
appProps | object | undefined | 传递给微应用的 props。Vue 绑定仅通过该属性向微应用传递数据。 |
::: infosettings的默认值与 React 绑定不同 Vue 绑定的settings默认值为{ sandbox: true },React 绑定则不设置默认值,不会替你填任何默认项。两者的sandbox在 qiankun 核心运行时中默认值都为true,因此不额外传值时行为一致。 :::
::: warning 业务数据通过appProps传递 React 绑定会将<MicroApp>上的附加 prop 传递给微应用,Vue 绑定则不会传递任意附加属性。业务数据必须放入appProps对象,未声明的其他属性会被忽略。当前实现还会将autoSetLoading、autoCaptureError和appProps对象本身传入微应用;业务代码不应依赖这些组件控制字段。 :::
settings、lifeCycles等组件自身消费的属性在共享层被统一定义在componentOwnedProps列表中(见 packages/ui-bindings/shared/src/index.ts),omitSharedProps会把这些字段从最终传给微应用的 props 中剔除,避免加载指示器插槽等渲染闭包泄漏进子应用。
settings(AppConfiguration)
settings与loadMicroApp的第二个参数结构相同。完整定义参见 AppConfiguration,字段为fetch、streamTransformer、nodeTransformer和sandbox(默认值为true)。样式隔离、额外全局变量、孵化上下文和隔离插件均位于sandbox对象内部。
<template> <micro-app name="app1" entry="http://localhost:8000" :settings="{ sandbox: { styleIsolation: true } }" /> </template>如需为特定微应用关闭 JavaScript 沙箱,可传入:settings="{ sandbox: false }"。相关行为参见 JavaScript 隔离和样式隔离。
在共享层 mountMicroApp 的实现中,settings会原样合并进loadMicroApp的configuration参数;lifeCycles则作为第三个参数直接透传——源码注释特别指出,历史上曾用concat(undefined, hook)包装每个钩子,结果 qiankun 把[undefined, hook]当作钩子调用,导致所有传入lifeCycles的应用挂载即失败,因此现在改为原样透传。
向微应用传递 props(appProps)
需要传递给微应用的数据应放入appProps:
<script setup> import { reactive } from 'vue'; import { MicroApp } from '@qiankunjs/vue'; const appProps = reactive({ userId: 42, theme: 'dark' }); </script> <template> <micro-app name="app1" entry="http://localhost:8000" :appProps="appProps" /> </template>这些数据会作为props参数传给微应用导出的生命周期函数:
// 微应用内部 export async function mount(props) { console.log(props.userId); // 42 }组件会深度侦听appProps(源码中watch(appProps, ..., { deep: true }))。修改嵌套值(例如appProps.theme = 'light')会尝试更新当前实例。执行更新前,微应用必须导出update生命周期、处于MOUNTED状态,并且尚未开始卸载。相关说明参见应用间共享状态与通信。
::: tipupdate仅在挂载完成后执行 共享层updateMicroApp会等待mountPromise完成,再按顺序处理更新,并且仅在 Parcel 状态为MOUNTED时调用update。挂载期间发生的中间状态变化不保证逐次触发更新。 :::
更新队列与防抖提示
源码层面,updateMicroApp通过_updatingPromise把每次更新串成一条链,保证后一个更新必须等待前一个更新完成,与组件状态变更顺序一致。首次更新以mountPromise为链的起点——注释明确指出这里只能"补上起点"而不能"跳过本次更新",否则宿主传入的第一次 props 变更会被直接吞掉。
另外,在开发环境下(NODE_ENV === 'development'),如果同一微应用在200ms 内更新次数过多,updateMicroApp会打印一条优化提示警告:
[@qiankunjs/ui-shared] It seems like microApp app1 is updating too many times in a short time(200ms)...这通常意味着主应用在频繁的响应式重渲染中反复改动了appProps,值得检查是否需要节流或解耦数据更新频率。
加载与错误界面
加载指示器和错误边界均需显式启用。如果未启用这两项功能,也未提供对应插槽,组件只渲染挂载容器<div>。如果设置了autoSetLoading、autoCaptureError、#loader插槽或#error-boundary插槽中的任意一项,组件会额外渲染class为qiankun-micro-app-wrapper的包裹元素,用于容纳加载节点、错误节点和挂载容器。
对应源码中,mountMicroApp在开始加载时调用setLoading(true),在mountPromise成功或loadPromise/bootstrapPromise任一失败时调用setLoading(false);错误则通过setError交给组件的setComponentError统一处理。
自动加载与错误捕获
通过以下两个布尔 prop 启用内置指示器:
<script setup> import { MicroApp } from '@qiankunjs/vue'; </script> <template> <micro-app name="app1" entry="http://localhost:8000" autoSetLoading autoCaptureError /> </template>内置界面仅提供基础占位内容:默认加载界面(MicroAppLoader.ts)渲染文本loading...,默认错误边界(ErrorBoundary.ts)渲染包含error.message的<div>。生产环境通常应使用下文介绍的插槽提供自定义界面。
::: info 加载状态的初始值 Vue 绑定将loading初始化为false,React 绑定的初始值则为true。微应用开始加载时,该状态会设为true;启用autoSetLoading后,组件会在mountPromise完成时将其恢复为false。未启用autoSetLoading时,组件不会渲染内置加载界面。 :::
自定义loader插槽
可通过#loader作用域插槽渲染自定义加载指示器。组件会将布尔值loading直接传给插槽:加载期间为true,加载结束后为false。
<script setup> import CustomLoader from '@/components/CustomLoader.vue'; import { MicroApp } from '@qiankunjs/vue'; </script> <template> <micro-app name="app1" entry="http://localhost:8000" autoSetLoading> <template #loader="loading"> <custom-loader :loading="loading" /> </template> </micro-app> </template>#loader插槽的优先级高于内置加载界面。提供该插槽后,组件不会渲染默认加载界面;仍需设置autoSetLoading,组件才会在mountPromise完成后自动将loading设为false。
自定义错误边界插槽
可通过#error-boundary作用域插槽渲染自定义错误界面。组件会将Error实例直接传给插槽。该插槽仅在发生错误后渲染。
<script setup> import CustomErrorBoundary from '@/components/CustomErrorBoundary.vue'; import { MicroApp } from '@qiankunjs/vue'; </script> <template> <micro-app name="app1" entry="http://localhost:8000"> <template #error-boundary="error"> <custom-error-boundary :error="error" /> </template> </micro-app> </template>未捕获的错误会重新抛出
如果既未启用autoCaptureError,也未提供#error-boundary插槽,则load、bootstrap和mount阶段的错误会从异步加载流程中重新抛出。建议启用组件内置错误界面或提供自定义错误界面,避免产生未处理的 Promise 拒绝。
::: warning 启用autoCaptureError或提供#error-boundary插槽后,组件会通过错误界面呈现异常,不再重新抛出。同一微应用应选择一种错误处理方式,不要同时依赖外层errorCaptured与组件内错误边界。详见处理微应用错误。 :::
一个值得注意的源码细节:为了兼容文档中一直使用的#error-boundary写法,组件在读取插槽时会同时接受errorBoundary与error-boundary两种拼写(见 MicroApp.ts 中slots.errorBoundary ?? slots['error-boundary']),因为 Vue 不会像规范化 prop 名那样规范化插槽名。
重新挂载与实例句柄
组件仅侦听name的变化来触发重新挂载。修改该 prop 会卸载当前微应用并创建新实例;仅修改entry、settings或lifeCycles不会创建新实例。组件销毁时,onBeforeUnmount会自动卸载微应用;卸载操作会等待正在进行的mountPromise完成,以保持挂载和卸载的执行顺序。
从源码看,挂载/卸载通过一条lifecyclePromise 链串行化(unmount().then(() => mountMicroApp(...)))。源码注释解释了这样做的原因:mountMicroApp在应用交接后还要再过一个 tick 才 resolve,如果用户在第一次切换尚未落定前又触发了一次name变更,没有这条链就可能把两个微应用竞态地挂进同一个容器——这正是<router-view>在用户快速连点时的典型场景。此外,name的 watcher 在触发时会立即快照当时的 props({ ...originProps, ...appProps.value }),避免排队的挂载执行时读到更晚一次切换的 props,导致最后一个应用被反复挂载又拆掉。
可以通过组件实例的microApp和microAppRef两个属性访问当前微应用实例,两者均指向同一个MicroAppParcel 句柄。可通过模板 ref 访问该句柄:
<script setup> import { ref, onMounted } from 'vue'; import { MicroApp } from '@qiankunjs/vue'; const microAppComp = ref(); onMounted(() => { // Parcel 句柄:getStatus()、mountPromise、unmount()、update() 等 console.log(microAppComp.value?.microApp?.getStatus()); }); </script> <template> <micro-app ref="microAppComp" name="app1" entry="http://localhost:8000" /> </template>该句柄是@qiankunjs/single-spa(qiankun 内置的 single-spa fork)的 Parcel。getStatus()返回NOT_LOADED、LOADING_SOURCE_CODE、NOT_BOOTSTRAPPED、BOOTSTRAPPING、NOT_MOUNTED、MOUNTING、MOUNTED、UPDATING、UNMOUNTING、UNLOADING、SKIP_BECAUSE_BROKEN或LOAD_ERROR。完整类型参见类型参考。
::: tip 由组件管理生命周期 应优先通过 prop(name、appProps)管理微应用,而不是直接调用句柄上的unmount()或update()。组件会按顺序执行卸载,并协调并发更新;直接调用句柄方法可能与组件的内部状态发生冲突。 :::
CSS 钩子
CSS 类名与 React 绑定一致。组件会添加两个稳定的类名,并在提供wrapperClassName或className时将自定义值添加到对应类名之前。
| 元素 | 始终应用的类名 | prop 提供的额外类名 |
|---|---|---|
| 包裹元素(仅在启用加载状态或错误边界时存在) | qiankun-micro-app-wrapper | wrapperClassName |
| 挂载容器 | qiankun-micro-app-container | className |
/* 所有微应用的挂载容器 */ .qiankun-micro-app-container { min-height: 320px; } /* 承载加载界面和错误界面的包裹元素 */ .qiankun-micro-app-wrapper { position: relative; }包裹元素仅在启用加载状态或错误边界时存在。因此,如果<micro-app>未配置加载或错误界面,wrapperClassName不会产生效果。
完整示例
以下示例组合了本文介绍的全部核心能力:appProps响应式传参、样式隔离、内置加载状态、自定义加载与错误插槽、以及自定义 CSS 类名:
<script setup> import { reactive } from 'vue'; import { MicroApp } from '@qiankunjs/vue'; import Spinner from '@/components/Spinner.vue'; import ErrorPanel from '@/components/ErrorPanel.vue'; const appProps = reactive({ userId: 42 }); </script> <template> <micro-app name="app1" entry="http://localhost:8000" :settings="{ sandbox: { styleIsolation: true } }" :appProps="appProps" autoSetLoading wrapperClassName="my-wrapper" className="my-container" > <template #loader="loading"> <spinner v-if="loading" /> </template> <template #error-boundary="error"> <error-panel :message="error.message" /> </template> </micro-app> </template>组件与 loadMicroApp 的封装关系
如果希望脱离组件直接操作实例,可阅读loadMicroApp的文档与 核心实现。组件本质上是它的薄封装:
name、entry对应LoadableApp的name、entry,容器由组件内部管理(模板 ref 拿到qiankun-micro-app-container的<div>);settings对应第二个参数AppConfiguration,其中sandbox是隔离能力的统一入口,对象形式可承载styleIsolation、globals、incubatorContext、plugins等;lifeCycles对应第三个参数LifeCycles;appProps经omitSharedProps过滤后作为props传给微应用生命周期。
这种封装让你既能享受声明式组件的便利(生命周期自动管理、深度侦听更新、加载/错误界面),又保留了loadMicroApp全部配置能力,需要精细控制时仍可通过microApp句柄拿到 Parcel 级别的状态与 Promise。
相关内容
- React
<MicroApp>组件——React 绑定及其 prop 传递方式。 - loadMicroApp——组件所封装的核心 API。
- AppConfiguration——
settings的类型定义。 - 微应用生命周期与 props——
mount、update和unmount的语义。 - 运行多个微应用实例——同时挂载多个微应用。
【免费下载链接】qiankun📦 🚀 Blazing fast, simple and complete solution for micro frontends.项目地址: https://gitcode.com/gh_mirrors/qi/qiankun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考