ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Umi (@umi/max) 微前端实战:Qiankun 插件从主子应用配置到通信、生命周期与错误处理的完整指南

Umi (@umi/max) 微前端实战:Qiankun 插件从主子应用配置到通信、生命周期与错误处理的完整指南 Umi (umi/max) 微前端实战Qiankun 插件从主子应用配置到通信、生命周期与错误处理的完整指南【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umiUmi 官方解决方案umi/max内置了 Qiankun 微前端插件实现见 qiankun 插件入口一条配置即可开启微前端开发模式帮助开发者在 Umi 项目中快速集成 Qiankun 微应用构建生产可用的微前端架构。读完本文你将掌握父/子应用的最小化配置、三种子应用引入方式、基于useModel()的父子应用通信、生命周期钩子的完整语义以及加载动画与错误捕获的自定义方案并了解插件在 umi/src 与 slave 两侧的真实实现机制。核心概念父应用、子应用与微应用微前端的基本模型可以这样理解在父应用里通过导航栏切换路由后页面下方显示的内容来自不同的子应用。子应用支持单独打开子应用之间也支持任意嵌套。更直观地讲父应用和子应用都是独立的前端项目父应用可以在内部引入子应用子应用也可以继续引入孙子应用以此类推。当一个应用能够作为子应用被其它应用引入时它就是所谓的微应用。快速开始本教程假设你对什么是微前端、什么是 Qiankun 微应用、以及如何使用 Qiankun 微应用已有基本了解。配置父应用首先需要配置父应用注册子应用的相关信息这样父应用才能识别并在内部引入子应用。注册子应用的方式主要有两种插件注册构建时静态配置与运行时注册通过src/app.ts动态导出。插件注册子应用修改父应用的 Umi 配置文件添加如下内容// .umirc.ts export default { qiankun: { master: { apps: [ { name: app1, entry: //localhost:7001, }, { name: app2, entry: //localhost:7002, }, ], }, }, };其中name为子应用名称引入子应用时使用entry为子应用运行的 HTTP 地址。master对象的完整 API 见下文 MasterOptions。从源码看插件对qiankun配置的 schema 校验非常宽松qiankun.ts 中仅声明了slave、master、externalQiankun三个字段master的具体选项在运行时由 masterRuntimePlugin.tsx 消费。运行时注册子应用如果希望把子应用列表放到运行时决定只需在配置中开启空master// .umirc.ts export default { qiankun: { master: {}, }, };然后在父应用的src/app.ts中导出qiankun对象// src/app.ts export const qiankun { apps: [ { name: app1, entry: //localhost:7001, }, { name: app2, entry: //localhost:7002, }, ], };在实现上masterRuntimePlugin.tsx 的getMasterRuntime()会通过插件管理器应用qiankun运行时配置拿到master或直接是整个导出对象后与静态的getMasterOptions()结果合并最终写入setMasterOptions()供MicroApp /组件读取。配置子应用子应用需要导出必要的生命周期钩子供父应用在适当时机调用。假设你的子应用基于 Umi 开发且引入了 qiankun 插件如果不是可以参照 Qiankun 官方入门文档自行配置需要手动实现bootstrap/mount/unmount并挂载到window[appName]。Utoopack 兼容性提示如果子应用使用 utoopack 构建且主应用使用 qiankun 2请将主应用的 qiankun 升级至2.10.17-beta.0或更高版本。更早的版本无法在执行入口脚本时正确提供document.currentScript会导致子应用加载失败。修改子应用的 Umi 配置文件// .umirc.ts export default { qiankun: { slave: {}, }, };仅此一行插件就会自动为子应用创建好 Qiankun 所需的完整生命周期钩子。从 slave.ts 源码可以看到插件实际做了大量工作注入生命周期通过addEntryCode向入口注入bootstrap/mount/unmount/update四个导出由genMount、genBootstrap、genUnmount、genUpdate生成见 lifecycles.ts非 Qiankun 环境下直接bootstrap().then(mount)独立运行设置 UMD 输出chainWebpack中将output.libraryTarget设为umd、library为包名api.pkg.name确保父应用可以通过window[appName]访问到子应用导出默认修改 base当 history 不是hash时自动将子应用base设为/${pkg.name}可用shouldNotModifyDefaultBase关闭注入 publicPath 修正脚本window.publicPath window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ || ...配合默认开启的runtimePublicPath保证子应用以任意方式部署时资源地址正确标记入口脚本modifyHTML给umi.js脚本标签添加entry属性Qiankun 依赖该标记识别应用入口utoopack 场景下还会额外注入一段生命周期代理脚本getUtoopackQiankunLifecycleProxyScript用 Proxy 把window[appName]的读取延迟到生命周期真正注册完成规避 utoopack 输出时序问题——这正是前文兼容性提示中版本要求的来源。引入子应用插件提供了三种引入子应用的方式路由绑定、MicroApp /组件、MicroAppWithMemoHistory /组件。路由绑定引入子应用手动配置.umirc.ts的routes通过路由绑定子应用。适用场景子应用包含完整的路由切换逻辑时父子应用路由相互关联时。例如想在/app1/project和/app2路由分别加载app1、app2// .umirc.ts export default { routes: [ { path: /, component: /layouts/index.tsx, routes: [ { path: /app1, component: /layouts/app-layout.tsx, routes: [ // 配置微应用 app1 关联的路由 { // 带上 * 通配符意味着将 /app1/project 下所有子路由都关联给微应用 app1 path: /project/*, microApp: app1, }, ], }, // 配置 app2 关联的路由 { path: /app2/*, microApp: app2, }, ], }, ], };配置好后子应用的路由 base 会在运行时被设置为主应用中配置的path。例如上面 app1 关联的 path 为/app1/project若 app1 内部有路由/user则浏览器 URL 必须是/app1/project/user才能访问到对应页面否则子应用匹配不到路由而渲染空白或 404。qiankun插件扩展了 Umi 原有的路由对象新增了microApp字段值为注册子应用的name。切换到对应路由后Umi 会用MicroApp /组件渲染此子应用并替换原来路由的component。实现上master.ts 的modifyRoutes会把含microApp字段的路由route.file改写为一个动态import加载模板生成的 getMicroAppRouteComponent.tsx。该组件内部用useMatch(routePath)计算pathnameBase作为子应用base默认MicroAppRouteMode.PREPEND路由 path 既作匹配规则又作子应用 basename见 constants.ts再透传给MicroApp /。MicroApp /组件引入子应用通过组件直接加载或卸载子应用。适用场景与路由绑定相同子应用包含完整路由逻辑、父子应用路由相互关联。import { MicroApp } from umi; export default function Page() { return MicroApp nameapp1 /; }该方式下父子应用的路由一一对应父应用路由为/some/page时子应用路由同样为/some/page切换子应用路由时父应用同步切换。如果父应用路由包含前缀可配置base属性保证路由正确对应。例如父应用路由为/prefix/router-path/some/page时希望子应用路由为/some/pageimport { MicroApp } from umi; export default function Page() { return MicroApp nameapp1 base/prefix/router-path /; }从 MicroApp.tsx 源码看组件核心调用的是 Qiankun 的loadMicroApp把container指向内部div、把settings中的base/history组装进FrameworkConfiguration组件卸载时通过microApp._unmounting标志位与unmountMicroApp()保证 unmount 后不再触发 update。MicroAppWithMemoHistory /组件引入子应用适用场景仅使用子应用的指定路由、父子应用路由相互独立。它是MicroApp /的变体需要显式提供url属性作为子应用路由当父应用路由变化时子应用路由不会改变。import { MicroAppWithMemoHistory } from umi; export default function Page() { return MicroAppWithMemoHistory nameapp2 url/some/page /; }子应用之间跳转当子应用通过路由绑定方式引入时在其它子应用内部可以使用MicroAppLink /跳转到对应路由。以app1、app2为例// 在 app1 中 import { MicroAppLink } from umi; export default function Page() { return ( {/* 跳转链接为 /app2/home */} MicroAppLink nameapp2 to/home Buttongo to app2/Button /MicroAppLink / ); }点击后父应用路由变为/app2/home渲染app2内部路由为/home的页面。从app2跳回app1同理// 在 app2 中 import { MicroAppLink } from umi; export default function Page() { return ( {/* 跳转链接为 /app1/project/home */} MicroAppLink nameapp1 to/home Buttongo to app1/Button /MicroAppLink / ); }也可以从子应用跳转到父应用的指定路由// 在子应用中 import { MicroAppLink } from umi; export default function Page() { return ( {/* 跳转链接为 /table */} MicroAppLink isMaster to/table Buttongo to master app/Button /MicroAppLink / ); }MicroAppLink.tsx 的实现细节它从useModel(qiankunStateFromMaster)中取出__globalRoutesInfo含microAppRoutes、base、masterHistoryType用urlFactory按子应用 name 找到其路由 path把path中的/*去除后作为前缀拼接到to之前生成最终 URLbrowser模式下点击拦截默认行为并执行history.pushStatehash模式则生成#开头的链接由a标签默认行为完成跳转。子应用生命周期Qiankun 在 single-spa 的基础上实现了额外的生命钩子。按微应用生命周期顺序完整的钩子列表为钩子调用时机状态变化beforeLoad微应用开始获取前初始为NOT_LOADEDload微应用获取完成时开始获取为LOADING_SOURCE_CODE成功变NOT_BOOTSTRAPPED失败变LOAD_ERRORbootstrap微应用初始化完成时开始初始化BOOTSTRAPPING完成变NOT_MOUNTEDbeforeMount每次开始挂载前-mount每次开始挂载时变为MOUNTINGafterMount每次挂载完成时变为MOUNTEDbeforeUnmount每次开始卸载前-unmount每次开始卸载时变为UNMOUNTINGafterUnmount每次卸载完成时变为NOT_MOUNTEDunload微应用卸载完成时变为NOT_LOADED此外还有一个特殊钩子update仅在使用MicroApp /或MicroAppWithMemoHistory /组件引入时生效状态为MOUNTED的微应用手动刷新时调用开始更新变为UPDATING完成回到MOUNTED。手动刷新子应用的示例import { useRef } from react; import { MicroApp } from umi; export default function Page() { const microAppRef useRef(); // 执行此方法时更新子应用 const updateMicroApp () { microAppRef.current?.update(); }; return MicroApp nameapp1 ref{microAppRef} /; }值得注意的是除了手动update()MicroApp /组件还会在stateForSlave或组件 props 变化时自动触发microApp.update(props)——MicroApp.tsx 用_updatingPromise链式队列保证多次 props 变更时更新按顺序串行执行且开发环境下 200ms 内连续多次更新会打印告警提示优化重渲染。自定义生命周期逻辑可以在父应用全局配置也可以在子应用单独配置。父应用配置生命周期钩子在父应用src/app.ts导出qiankun对象进行全局配置所有子应用都将实现这些钩子// src/app.ts export const qiankun { lifeCycles: { // 所有子应用在挂载完成时打印 props 信息 async afterMount(props) { console.log(props); }, }, };在 MicroApp.tsx 中全局钩子globalLifeCycles与组件 props 里的lifeCycles通过lodash/mergeWith以数组合并后传给loadMicroApp的第三个参数即同名钩子会被依次串联执行。子应用配置生命周期钩子在子应用src/app.ts导出qiankun对象实现钩子。子应用运行时仅支持配置bootstrap、mount、unmount钩子// src/app.ts export const qiankun { // 应用加载之前 async bootstrap(props) { console.log(app1 bootstrap, props); }, // 应用 render 之前触发 async mount(props) { console.log(app1 mount, props); }, // 应用卸载之后触发 async unmount(props) { console.log(app1 unmount, props); }, };类型层面slave.ts 生成的运行时类型声明IRuntimeConfig.qiankun为「完整SlaveOption含enable与生命周期」与「仅生命周期钩子」的 XOR即qiankun: { bootstrap, mount, unmount }与qiankun: { slave: { enable } }两种写法二选一。父子应用通信父子应用通信有两种实现方式基于useModel()的通信——Umi推荐的方案基于配置的通信。基于useModel()的通信该方式基于 数据流插件此插件已内置于umi/max解决方案中详见 数据流指南。注意该通信方式要求子应用基于 Umi 开发且引入了数据流插件从 slave.ts 可以看到若model插件未启用生成的代码会把useModel置为null并打印警告相关功能将不可用。主应用透传数据路由模式引入时需要在父应用src/app.ts导出useQiankunStateForSlave()函数其返回值将传递给子应用// src/app.ts export function useQiankunStateForSlave() { const [globalState, setGlobalState] useStateany({ slogan: Hello MicroFrontend, }); return { globalState, setGlobalState, }; }组件模式引入时直接将数据作为组件参数传入import { useState } from react; import { MicroApp } from umi; export default function Page() { const [globalState, setGlobalState] useStateany({ slogan: Hello MicroFrontend, }); return ( MicroApp nameapp1 globalState{globalState} setGlobalState{setGlobalState} / ); }实现原理对应 master.ts 的addExtraModels逻辑主应用侧插件发现src/app.ts导出了useQiankunStateForSlave后会以命名空间qiankunStateForSlave注册一个额外 ModelMicroApp /内部通过useModel(qiankunStateForSlave)读取该状态并与配置中的props合并后传给loadMicroApp见 MicroApp.tsx合并顺序为propsFromConfig stateForSlave propsFromParams。子应用消费数据子应用会自动生成一个全局 Model命名空间为qiankunStateFromMaster实现见 qiankunModel.ts它用模块级变量缓存初始值并在setModelState被调用时同步到 React state。通过useModel()可在任意组件中获取并消费父应用透传的数据import { useModel } from umi; export default function Page() { const masterProps useModel(qiankunStateFromMaster); return div{JSON.stringify(masterProps)}/div; }或者通过高阶方法connectMaster()获取数据import { connectMaster } from umi; function MyPage(props) { return div{JSON.stringify(props)}/div; } export default connectMaster(MyPage);子应用也可以在生命周期钩子中直接使用传入的props按需实现上文的生命周期钩子即可。特别的当父应用使用MicroApp /或MicroAppWithMemoHistory /引入子应用时会额外向子应用传递setLoading()方法允许子应用在合适时机标记自身加载完成const masterProps useModel(qiankunStateFromMaster); masterProps.setLoading(false); // 或者 function MyPage(props) { props.setLoading(false); } connectMaster(MyPage);子应用挂载完成变为MOUNTED状态时也会自动标记完成。对应地MicroApp.tsx 中setLoading被放进loadMicroApp的propsloadPromise/bootstrapPromise/mountPromise任一失败时也会执行setLoading(false)结束加载态。基于配置的通信在父应用注册子应用时可以传入props属性将数据传递给子应用。例如修改父应用src/app.ts的qiankun导出// src/app.ts export const qiankun { apps: [ { name: app1, entry: //localhost:7001, props: { accountOnClick: (event) console.log(event), accountName: Alex, accountAge: 21, }, }, ], };子应用同样在生命周期钩子中获取并使用传入的props即可。自定义子应用启用加载动画或错误捕获能力后子应用的渲染结构会变为div style{{ position: relative }} className{wrapperClassName} MicroAppLoader loading{loading} / ErrorBoundary error{e} / MicroApp className{className} / /div这与 MicroApp.tsx 的 JSX 完全一致仅在microAppLoader或microAppErrorBoundary存在时渲染外层qiankun-micro-app-wrapper容器。子应用加载动画启用后子应用加载期间自动显示加载动画子应用挂载完成变为MOUNTED时加载结束显示子应用内容。基于 antd 的加载动画当使用 antd 作为组件库时向子应用传入autoSetLoading属性即可插件会自动调用 antd 的Spin /组件作为加载组件master.ts 在antd插件启用时生成 AntdLoader.tsx否则默认 loader 只是一个打印警告的空组件。路由模式// .umirc.ts export default { routes: [ { path: /app1, microApp: app1, microAppProps: { autoSetLoading: true, }, }, ], };组件模式import { MicroApp } from umi; export default function Page() { return MicroApp nameapp1 autoSetLoading /; }自定义加载动画未使用 antd 或希望覆盖默认样式时可设置自定义loader组件。路由模式引入的子应用只支持运行时配置// .app.tsx import CustomLoader from src/components/CustomLoader; export const qiankun () ({ routes: [ { path: /app1, microApp: app1, microAppProps: { loader: (loading) CustomLoader loading{loading} /, }, }, ], });组件模式直接传参import CustomLoader from /components/CustomLoader; import { MicroApp } from umi; export default function Page() { return ( MicroApp nameapp1 loader{(loading) CustomLoader loading{loading} /} / ); }其中loading为boolean参数true表示仍在加载false表示加载结束。若希望多个子应用统一使用同一加载动画可在主应用配置defaultLoader文件路径// .umirc.ts qiankun: { master: { defaultLoader: /defaultLoader, }, },defaultLoader为文件路径约定放在 src 目录下参见 目录结构说明umi 中即代表src目录。master.ts 会断言该路径必须以/开头否则抛出「only support src path」错误。// defaultLoader.tsx import { Spin } from antd; export default function (loading: boolean) { return Spin spinning{loading} /; }注意loader的优先级高于defaultLoader见 MicroApp.tsx 中loader || defaultLoader || (autoSetLoading ? ...)的取值顺序。子应用错误捕获启用后子应用加载出现异常时自动显示错误信息。基于 antd 的错误捕获组件使用 antd 时传入autoCaptureError属性插件会自动调用 antd 的Result /组件AntdErrorBoundary.tsx作为错误捕获组件文案语言会自动读取 umi locale 配置切换。路由模式// .umirc.ts export default { routes: [ { path: /app1, microApp: app1, microAppProps: { autoCaptureError: true, }, }, ], };组件模式import { MicroApp } from umi; export default function Page() { return MicroApp nameapp1 autoCaptureError /; }自定义错误捕获组件未使用 antd 或希望覆盖默认样式时可设置自定义errorBoundary。路由模式引入的子应用只支持运行时配置// .app.tsx import CustomErrorBoundary from /components/CustomErrorBoundary; export const qiankun () ({ routes: [ { path: /app1, microApp: app1, microAppProps: { errorBoundary: (error) CustomErrorBoundary error{error} /, }, }, ], });组件模式直接传参import CustomErrorBoundary from /components/CustomErrorBoundary; import { MicroApp } from umi; export default function Page() { return ( MicroApp nameapp1 errorBoundary{(error) CustomErrorBoundary error{error} /} / ); }其中error为Error类型参数。多个子应用统一使用自定义错误组件时可在主应用配置defaultErrorBoundary同样是/开头的文件路径校验逻辑与defaultLoader相同// .umirc.ts qiankun: { master: { defaultErrorBoundary: /defaultErrorBoundary, }, },// defaultErrorBoundary.tsx export default function (error: Error) { return div{error?.message}/div; }注意errorBoundary的优先级高于defaultErrorBoundary。若两者都未配置且未开启autoCaptureErrorMicroApp.tsx 的setComponentError会直接把异常向上抛出而非吞掉。环境变量如果有些配置无法显式写进.umirc.ts或src/app.ts例如按部署环境动态注入可以存放在环境变量文件中。父应用.env示例INITIAL_QIANKUN_MASTER_OPTIONS{\apps\:[{\name\:\app1\,\entry\:\//localhost:7001\},{\name\:\app2\,\entry\:\//localhost:7002\}]}插件内部会执行JSON.parse(process.env.INITIAL_QIANKUN_MASTER_OPTIONS)并将结果与已有配置合并。上面的环境变量等价于export default { qiankun: { master: { apps: [ { name: app1, entry: //localhost:7001 }, { name: app2, entry: //localhost:7002 }, ], // ... .umirc.ts 中其它的配置信息 }, }, };注意存在相同配置项时如apps写在.umirc.ts中的配置覆盖环境变量中的配置。这一点在 master.ts 的modifyDefaultConfig中可以直接验证先展开JSON.parse(process.env.INITIAL_QIANKUN_MASTER_OPTIONS || {})再展开config.qiankun.master后者生效。另外isMasterEnable 表明即使未配置qiankun.master只要设置了INITIAL_QIANKUN_MASTER_OPTIONS环境变量master 插件也会被激活。子应用同理可编写.envINITIAL_QIANKUN_SLAVE_OPTIONS{\enable\:false}等价于qiankun.slave.enable false的默认配置合并见 slave.ts 中devSourceMap: true会先作为子应用默认值再依次与环境变量、用户配置合并。API 参考MasterOptions属性必填说明类型默认值enable否启用 Qiankun 微应用插件设为false时不启用booleanundefinedapps是微应用配置App[]undefinedroutes否微应用运行时的路由Route[]undefineddefaultErrorBoundary否子应用默认的错误捕获组件值为文件路径string-defaultLoader否子应用默认的加载动画值为文件路径string-sandbox否是否开启沙箱模式boolean \| { strictStyleIsolation: boolean, experimentalStyleIsolation: boolean }trueprefetch否是否启用微应用预加载boolean \| all \| string[] \| (( apps: RegistrableApp[] ) { criticalAppNames: string[]; minorAppsName: string[] })true关于沙箱和预加载可参阅 Qiankun 官方 API 文档。从源码看prefetch的实际行为在 masterRuntimePlugin.tsx 与 MicroApp.tsx 中all时对所有未配置base的应用执行prefetchAppsstring[]时只预取名单内应用true时则延迟到第一个微应用mountPromise完成后预取其它未挂载应用且受prefetchThreshold默认 5阈值限制避免无脑全量预取。SlaveOptions属性必填说明类型默认值enable否启用 Qiankun 微应用插件设为false时不启用booleanundefined源码中还存在若干未列入文档的实用选项例如 slave.ts 使用的shouldNotModifyDefaultBase阻止自动设置base /${pkg.name}、shouldNotModifyRuntimePublicPath不注入 publicPath 脚本、shouldNotAddLibraryChunkName生产环境默认true本地配合 MFSU 时可改库名规则、masterEntry本地开发时把请求代理到主应用地址见addMiddlewares中的 proxy 实现等可按需查阅源码。App属性必填说明类型默认值name是微应用的名称string-entry是微应用的 HTML 地址string{ script: string[], styles: [] }credentials否拉取微应用时同时拉取 Cookiesbooleanfalseprops否父应用传递给微应用的数据详见父子应用通信object{}credentials: true的实现在 masterRuntimePlugin.tsx插件会包装window.fetch当请求 URL 命中开启了credentials的应用 entry 时自动附加mode: cors与credentials: include。Route属性必填说明类型默认值path是路由 PATHstring-microApp是关联的微应用名称string-microAppProps否微应用的配置MicroAppProps{}MicroAppProps属性必填说明类型默认值autoSetLoading否自动设置微应用的加载状态booleanfalseloader否自定义的微应用加载状态组件(loading) React.ReactNodeundefinedautoCaptureError否自动设置微应用的错误捕获booleanfalseerrorBoundary否自定义的微应用错误捕获组件(error: any) React.ReactNodeundefinedclassName否微应用的样式类stringundefinedwrapperClassName否包裹微应用加载组件、错误捕获组件和微应用的样式类仅在启用加载组件或错误捕获组件时有效stringundefined补充说明MicroApp.tsx 的Props类型还接受base、historyhash | browser | memory或对应 history 对象、settingsQiankun 的FrameworkConfiguration、onHistoryInit等属性组件内其余透传属性...propsFromParams都会进入传给子应用的 props。FAQ生命周期钩子已执行但页面没有渲染如果页面没有报错且查看 DOM 发现子应用根节点已经存在、只是内容为空基本可以确定是当前 URL 没有匹配到子应用的任何路由导致的。比如主应用中配置了{ path: /app1, microApp: app1, }子应用的路由配置是{ path: /user, component: ./User, }那么必须通过/app1/user路径才能正常访问到子应用的 user 页面。这正是「子应用路由 base 在运行时被设置为主应用配置的path」这一机制的直接结果参见前文 getMicroAppRouteComponent.tsx 中prefix与base的拼接逻辑。排查此类问题时先确认浏览器 URL 是否等于「主应用路由 path 子应用内部路由」再检查子应用自身的base配置是否与主应用传入的一致。【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表