ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

qiankun微前端实战:生命周期与数据通信的踩坑与模板

qiankun微前端实战:生命周期与数据通信的踩坑与模板 说实话我最初拿到把三个技术栈完全不一样的后台系统整合成一个统一平台这个需求的时候第一反应是iframe套娃后来真正用qiankun做过几个项目之后才意识到微前端框架的价值不在于炫技而是让不同团队、不同技术栈的代码能在同一个浏览器页面里和平共生互不干扰。qiankun 作为国内落地最广的微前端方案核心踩坑点基本集中在两块生命周期怎么设计、数据通信怎么打通。这篇文章我会围绕这两个最容易翻车的环节把我实际项目里踩过的坑、验证过的代码和最终沉淀成模板的方案完整分享出来适合正在做微前端选型、或者已经把 qiankun 跑起来但卡在子应用加载、跨应用通信这几个细节上的读者。1. 为什么微前端方案里我最后选了 qiankun先聊聊选型因为后面所有生命周期和数据通信的讨论都建立在为什么用 qiankun 而不是别的方案这个前提上。很多团队一上来就纠结微前端到底选 iframe 还是 qiankun我觉得这个问题得拆成两步看先看你有没有微前端的需求再看你愿意为这个需求付出多少改造成本。1.1 微前端到底在解决什么问题微前端解决的不是页面拆小块这种技术问题而是组织协作问题。最典型的场景是公司里有 A 团队维护 Vue2 的订单系统B 团队维护 React18 的运营后台C 团队还有一套老 jQuery 项目不能重构。老板说给我合并成一个大平台用户只需要登一次录菜单统一、权限统一。这时候如果你让三个团队去改同一个代码仓库光是依赖版本冲突就能吵一个月的架。微前端的思路是每个团队继续维护自己的代码库、自己的构建产物只是在运行时把它们加载到一个基座页面里由基座来统一管理生命周期、路由切换和状态同步。qiankun 在这个领域里属于开箱即用的选手。它把 single-spa 里很多需要自己动手的部分比如 HTML 入口加载、JS 沙箱、样式隔离、预加载这些能力全部内置好了。你不需要手动去 fetch 子应用的 HTML 然后解析 script 标签也不需要自己写一套 Proxy 沙箱去隔离全局变量qiankun 都帮你处理了。1.2 qiankun 相比 single-spa 和 iframe 的取舍我最早是用 single-spa 做了一个 Demo做完我就放弃了。single-spa 本身只是路由调度器它只规定了子应用要暴露几个生命周期函数然后按路由激活。但子应用的 HTML 怎么加载、JS 怎么执行、样式怎么隔离它统统不管。你还需要引入一堆插件比如 import-html-entry 自己解析入口、自己处理全局变量污染改造量非常大。iframe 则是另一个极端实现简单但通信和体验都很糟。我见过很多项目用 iframe 嵌套最后都会遇到几个恶心问题父子页面之间传数据要靠 postMessage 来回绕localStorage 不互通子页面内部跳转刷新之后父页面的菜单状态容易丢还有双滚动条、弹窗居中错位这些细节每一个都要单独 hack。qiankun 的取舍是子应用依然是独立构建、独立部署但运行时它被加载到主应用的 DOM 容器里JS 在沙箱中执行样式被隔离所以从用户体验上看它跟原生单页应用几乎没什么区别。代价就是子应用需要做一点改造主要是暴露生命周期函数、配置 webpack 打包方式、处理静态资源路径这些改造成本相对可控。1.3 为什么重点看生命周期和数据通信微前端真正难的地方恰恰是我在标题里列出的这两块。生命周期决定了子应用什么时候被创建、什么时候被销毁、被挂载到哪个 DOM 容器如果设计不对子应用切换时就会出现页面空白、状态残留、事件重复绑定。数据通信则决定了主应用和子应用之间、子应用和子应用之间怎么交换用户信息、菜单权限、业务数据通信设计不好就会出现改了全局数据但页面不刷新子应用之间互相污染全局变量这类问题。下面我分别展开。2. 有效生命周期子应用不是加载完就完了qiankun 的生命周期官方文档里叫 bootstrap、mount、unmount后来社区也流行一个词叫有效生命周期大意是指真正影响业务状态的那几个阶段。很多新手第一次看文档觉得不就是导出三个函数吗但实际用起来这三个函数里放什么代码、什么时候执行、执行几次里面全是讲究。2.1 生命周期整体流程和触发时机先看整体流程。一个子应用被 qiankun 加载会经历这几个阶段bootstrap子应用第一次被激活时执行而且全局只会执行一次。通常在这里做一些只初始化一次的准备工作比如创建全局的实例、设置一些不会变的配置。mount每次进入子应用时执行。这里要做的核心工作是把子应用的根组件挂载到主应用提供的 DOM 容器里同时启动对应的路由。unmount每次切走时执行。这里要做的是销毁实例、清理事件监听、解绑全局状态把 DOM 容器恢复成干净状态。有人会问为什么 bootstrap 只执行一次mount 却可以执行多次因为用户在微前端平台里切换菜单本质上是在进入-离开-再进入子应用。第一次进入时子应用的代码只需要加载和执行一次但页面实例可以反复创建和销毁。这就像你在公司进了三次会议室投影仪只需要开一次机bootstrap但每次开会都要把 PPT 打开mount开完会要关掉unmount。另外 qiankun 还支持 update 生命周期但实际项目中我基本没用过。因为 update 主要用于子应用在保持挂载状态下接收新 props的场景这个在日常业务里很少遇到更多的时候我们直接通过全局状态通信去触达已经挂载的子应用不需要额外维护一个 update 钩子。2.2 bootstrap、mount、unmount 里该干什么、不该干什么我见过最典型的错误是把创建根实例放在 bootstrap 里然后 mount 里什么都不做。这个思路在单应用里是对的但在微前端里是错的。因为 bootstrap 只执行一次如果你的 Vue 根实例创建之后绑定了特定的 DOM 容器第一次切换走之后这个 DOM 容器被清空了第二次再进入时根实例还挂着旧的 DOM 引用页面就渲染不出来了。正确的做法是bootstrap 里只放跟 DOM 无关的一次性逻辑比如初始化日志上报、设置全局的 axios 拦截器mount 里每次重新创建根实例然后挂载到 qiankun 传进来的容器unmount 里销毁这个根实例并清空容器。这样每次进入都是全新状态不会出现二次进入页面空白的问题。下面是一段 Vue2 子应用的标准写法我实际项目里一直在用直接给你参考// subapp/src/main.js import Vue from vue; import App from ./App.vue; import router from ./router; let instance null; function render(props {}) { const { container } props; // 注意使用 qiankun 传入的 container 查找挂载点 // 避免在多实例场景下选错 DOM instance new Vue({ router, render: (h) h(App) }).$mount(container ? container.querySelector(#app) : #app); } // 独立运行时直接渲染方便本地开发调试 if (!window.__POWERED_BY_QIANKUN__) { render(); } // 以下是 qiankun 需要的生命周期导出 export async function bootstrap() { console.log([subapp] bootstrap); } export async function mount(props) { console.log([subapp] mount, props); render(props); } export async function unmount() { console.log([subapp] unmount); instance.$destroy(); instance.$el.innerHTML ; instance null; }你注意这里有个关键判断window.__POWERED_BY_QIANKUN__。这个变量是 qiankun 在沙箱环境里注入了用来告诉子应用你现在是被微前端框架托管的状态。如果是在本地浏览器直接打开子应用地址这个变量不存在就走独立渲染逻辑。不写这个判断本地开发子应用时页面会白屏因为子应用的入口没有主动去挂载。2.3 路由自动加载与手动加载的生命周期差异qiankun 提供了两种加载方式一种是通过registerMicroApps注册子应用框架根据路由 activeRule 自动加载和卸载另一种是通过loadMicroApp手动加载子应用适合那种某个区块需要显式挂载/移除的场景比如你在一个 Dashboard 里动态嵌入一个小模块。这两种方式生命周期触发逻辑一样都是 bootstrap→mount→unmount但注意几点区别自动加载模式下qiankun 会在路由切换时自动触发 unmount你不需要手动处理。手动加载模式下子应用会常驻在内存里即使你从页面 A 切到页面 B它也不会自动卸载除非你调用返回的微应用实例的unmount方法。我在实际项目里用loadMicroApp加载了一个数据报表模块切换菜单后因为没调用 unmount导致那个报表模块的定时轮询一直没停后台接口被打爆了。所以手动加载一定要在合适的时机通常是组件销毁钩子调用microApp.unmount()。手动加载还可以传独立的 props跟registerMicroApps注册时传的 props 互不冲突。这个机制很适合一个子应用被多个位置引用、每个位置要传不同参数的场景。2.4 生命周期里的常见误区我梳理几个自己踩过、也帮同事排查过的高频误区每一条都值得你留意。第一个误区是在 unmount 里忘了清理全局事件监听。因为 qiankun 的沙箱能隔离 JS 运行时的一部分副作用但 window.addEventListener 这类原生事件它不能自动回滚。如果你在 mount 里绑定了window.addEventListener(resize, handler)但 unmount 里没有 removeEventListener那么子应用切走之后每次窗口尺寸变化handler 还会执行而且下一次再 mount 时又会绑定一遍最终导致事件处理器越积越多、页面越来越卡。第二个误区是子应用没有配置 webpack 的 library 导出。qiankun 要求子应用把生命周期函数挂到全局 window 上这依赖 webpack 的 output.library 配置。你如果直接跑一个 CRA 生成的项目不改造qiankun 会报子应用导出生命周期函数失败之类的错误。解决方案是在 CRA 里重写 config-overrides或者用craco/craco调整 webpack 配置把 library 设为your-subapp-namelibraryTarget 设为umd并把 publicPath 设成动态获取否则子应用的静态资源会 404。第三个误区是生命周期函数没有返回 Promise。qiankun 的调度机制依赖 Promise 来判断这个子应用是否完成挂载/卸载。如果你导出的 mount 函数是同步的、没有 return Promiseqiankun 虽然有时候也能正常运行但在切换比较快的时候可能因为状态不同步出现白屏或报错。所以规范做法是让每个生命周期函数都用 async 修饰保证返回 Promise。3. 数据通信props 不是唯一的路生命周期解决的是子应用什么时候活着数据通信解决的是子应用活着的时候怎么跟外界说话。qiankun 官方推荐的通信方式有一个核心原则主应用作为中心通过 props 和全局状态往下分发数据。不要自己去搞一套 子应用之间互相访问 window 的通信那样会导致数据流不可追溯排查问题非常痛苦。3.1 主应用通过 props 给子应用发数据这是最直接、也最推荐的第一种方式。在registerMicroApps注册子应用时可以给每个子应用传入独立的 props// 主应用 main.js import { registerMicroApps, start } from qiankun; registerMicroApps([ { name: order-app, entry: //localhost:8081, container: #micro-container, activeRule: /order, props: { userInfo: { id: 10001, name: 张三, role: admin }, token: token-from-main, logout: () { // 子应用可以调用这个方法让主应用执行退出登录 location.href /login; } } } ]); start();子应用在 mount 时就能拿到这些 propsexport async function mount(props) { console.log(props.userInfo); // { id: 10001, ... } console.log(props.token); props.logout(); // 调用主应用传下来的回调 }这种方式的好处是数据流清晰主应用给子应用什么子应用就用什么不会出现子应用主动去改主应用状态的情况。我建议把用户信息、权限点、基础配置这类稳定的数据都走 props 传因为它们是子应用启动时就必须具备的上下文。3.2 子应用向主应用回传数据回调函数props 是单向的那子应用想通知主应用我这边发生了某件事怎么办最简单的方式就是利用 props 里的回调函数。主应用提前在 props 里塞一个函数子应用在合适时机调用它并且可以带着参数回传数据。比如子应用完成一单退款操作后希望主应用更新待办角标// 主应用 props 里传 props: { onOrderRefunded: (orderNo) { // 主应用收到通知刷新角标或统计 refreshBadge(orderNo); } }子应用这边退款成功后调用export async function mount(props) { window.$refundHandler (orderNo) { props.onOrderRefunded(orderNo); }; }这种方式比自定义事件好排查因为它是一个纯粹的 JavaScript 函数调用有调用栈、有参数debug 起来非常舒服。我实际项目里大部分子应用通知主应用的需求靠回调函数就解决了根本没有用到额外的事件总线。3.3 全局状态通信initGlobalState 与 setGlobalState如果数据需要多个子应用共享或者主应用需要主动推送数据给一个已经被挂载的子应用props 就不太合适了。因为 props 只在 mount 时传递一次你不可能每次数据变化都重新 mount 子应用。这时候应该用 qiankun 提供的全局状态。主应用先初始化全局状态import { initGlobalState } from qiankun; // action 是一个全局状态控制器 const actions initGlobalState({ count: 0, currentMenu: /order }); actions.onGlobalStateChange((state, prevState) { console.log(主应用监听全局状态变化, state, prevState); }); // 主应用主动更新全局状态 actions.setGlobalState({ count: 1 }); // 需要导出给子应用使用 export { actions };子应用这边通过 props 拿到 qiankun 注入的onGlobalStateChange和setGlobalStateexport async function mount(props) { // 监听全局状态变化 props.onGlobalStateChange((state, prevState) { console.log(子应用收到新的全局状态, state); // 这里可以触发组件更新 }, true); // 第二个参数 true 表示立即执行一次拿到当前值 // 子应用也可以修改全局状态 props.setGlobalState({ count: 2 }); }这里面有个小坑onGlobalStateChange的第二个参数很多人不知道是干嘛用的。如果传true回调会在注册时立即执行一次这样你能马上拿到当前全局状态如果不传或传false就只有等状态变化时才触发。我自己习惯在 mount 里传true保证子应用一进来就能同步最新的全局数据不用等下一次事件。另外注意setGlobalState 只会浅合并数据也就是只做了一层 Object.assign如果你改了嵌套对象里某个字段最好整体替换这个对象否则其他子应用可能监听不到。3.4 自定义事件兜底与多实例通信隔离有段时间我在项目里发现多个子应用如果同时监听同一个全局状态 key改起来容易互相干扰。比如订单应用和报表应用都监听setGlobalState({ theme: dark })这种配置如果其中一个应用误改了别人的数据排查起来很费劲。所以我后来会在全局状态里做命名空间约定比如order_*、report_*把不同业务的数据隔离开。如果还有一些一次性、临时性的通知比如打开某个弹窗刷新某个列表用全局状态反而沉余我倾向于用自定义事件// 子应用 A 发送 window.dispatchEvent(new CustomEvent(app:refresh-list, { detail: { page: 1 } })); // 子应用 B 监听 window.addEventListener(app:refresh-list, handler);但用自定义事件必须严格遵守两条纪律第一事件名全局唯一统一命名规范比如app:[业务]:[动作]第二组件卸载时必须移除监听。不然子应用切走后事件处理器残留下次再进入时会重复执行导致列表被刷新两次、接口被调两次。这个坑我自己踩过很多次后面我会单独列出来讲。4. 踩坑实录生命周期和数据通信的典型坑这章我专门把实际项目里发生过、且花了不少时间排查的问题整理成清单。每一个都是真实案例你遇到类似现象时可以直接按这个思路排查。4.1 mount 后拿不到 props先看独立运行判断有一次子应用开发同学跟我说我 mount 里打印 props 是有的但组件里拿不到用户信息。 我排查后发现问题出在他的子应用用了一个全局变量把 props 临时存下来但组件初始化时这个全局变量还没赋值等组件真正运行时才被赋值所以拿到的是 undefined。这里有两个更常见的细节原因值得注意子应用在 mount 里渲染根组件时如果传给根组件的方式不对比如没有通过 Vue 的 provide/inject 或 React 的 Context组件内自然拿不到 props。如果你在子应用入口文件顶层写了const globalProps props这种代码但这段代码不在 mount 函数内部而是放在单独的模块初始化里那 props 在模块加载时根本不存在拿到的自然是 undefined。我的建议很简单把 props 存到一个可导出的模块级变量但赋值动作必须在 mount 生命周期里完成并且组件需要的时候再晚一步读取。比如你可以封装一个useQiankunProps的 hook内部维护一个全局变量mount 时写入组件里通过 hook 读取保证时序安全。4.2 全局事件监听没清理导致的重复触发这个坑几乎每个微前端项目都会遇到。现象是第一次进入子应用功能正常第二次进入同一个页面发现点击按钮会触发两次请求第三次进入会触发三次。原因就是 mount 里用window.addEventListener绑定了事件但 unmount 里没移除。我排查这个问题时的标准动作是先检查子应用 unmount 生命周期里有没有对称地移除事件监听再检查是不是用了一些三方库比如 Element UI 的全局事件、ECharts 的 resize 监听这些库有时候会自己往 window 上挂监听。一个偷懒但有效的排查方法是在 unmount 里打印window.__listeners或者直接getEventListeners(window)Chrome 支持看看有没有大量遗留事件。4.3 子应用之间直接通信的坑一开始我把所有通信都收敛到主应用因为遇到过一次子应用之间直接互相通信导致的路由错乱问题子应用 A 通过 window 全局函数调用了子应用 B 的方法结果 B 内部做了一些 router 跳转因为 B 是在沙箱里执行它的路由 base 和主应用路由混在一起最后页面跳到了一个完全不存在的路径整个布局崩了。qiankun 的沙箱虽然隔离了全局变量但如果你主动在 window 上挂方法就等于自己打开了一条跨沙箱通道这在业务逻辑复杂之后会变得非常不可控。所以我的结论是子应用之间不要直接通信一切通过主应用中转。A 需要 B 的数据就让 A 调用主应用传来的回调主应用更新全局状态B 再通过onGlobalStateChange感知到变化。链路虽然是长了点但每一条数据流都能在代码里被追踪到出问题时不会一头雾水。4.4 常见问题速查表我把前面提到的和没提到的典型问题统一整理成一张速查表方便你对照排查问题现象常见原因解决方案子应用生命周期函数没有执行webpack 未配置 library/libraryTarget配置 umd 导出挂到 window 上子应用切换后白屏mount 里没有重新创建根实例每次 mount 重新 mount 到容器二次进入页面重复请求/事件重复unmount 未清理监听器对称移除事件监听销毁定时器子应用静态资源 404publicPath 写死将 publicPath 设为动态路径全局状态变化不触发子应用回调onGlobalStateChange 没注册或参数不对注册监听并考虑传 true 立即执行子应用间状态打架全局状态 key 冲突加业务命名空间如 order_*独立访问子应用白屏没有判断__POWERED_BY_QIANKUN__独立运行时主动渲染根组件这张表我每次跟新同学对接微前端项目时都会发一遍。排查问题时别急着改代码先对着表定位一下往往五分钟就能找到根因。5. 实操步骤从零跑通一套可复用的通信模板理论说再多不如直接给你一套能跑的模板。下面这个方案我至少在三个项目里复用过了包括一个 Vue2 主应用配 Vue2 子应用、两个 React 子应用混合的场景。5.1 主应用注册子应用的关键配置主应用侧核心是注册和启动。我的建议是在main.js里单独抽一个micro-app.js专门管理 registerMicroApps 和全局状态别把配置堆在主入口里不然项目大了以后很难维护。// 主应用 src/micro-app.js import { registerMicroApps, start, initGlobalState } from qiankun; const apps [ { name: vue-order, entry: //localhost:8081, container: #micro-container, activeRule: /order, props: baseProps(order) }, { name: react-report, entry: //localhost:8082, container: #micro-container, activeRule: /report, props: baseProps(report) } ]; function baseProps(appName) { return { appName, userInfo: getCurrentUser(), token: getToken(), // 统一提供的主应用能力 navigateTo: (path) history.pushState(null, , path), logout: () { location.href /login; }, // 子应用向主应用上报业务事件 emit: (eventName, payload) { actions.setGlobalState({ [${appName}_${eventName}]: payload }); } }; } const actions initGlobalState({ sharedData: {}, order_refresh: null, report_refresh: null }); registerMicroApps(apps); start({ prefetch: all, // 预加载所有子应用提升切换速度 sandbox: { experimentalStyleIsolation: true } // 开启样式隔离 });几点说明experimentalStyleIsolation是 qiankun 的样式隔离方案它会给子应用的样式自动加上容器选择器前缀可以有效避免子应用之间样式互相污染。但注意它并不能处理所有问题比如子应用里动态插入的 style 标签可能还是会出现漏网之鱼所以样式隔离还是需要配合团队规范一起约束。5.2 子应用改造生命周期导出子应用侧的改造我以一个 Vue2 项目为例核心就是前面写过的入口文件改造。React 项目思路一样区别是把new Vue换成createRootunmount 里调用root.unmount()。这里我补充一个容易被忽略的点Vue Router 的 base 要跟主应用路由匹配。如果你的主应用通过/order激活子应用子应用内部路由是/order/list、/order/detail那子应用的 Vue Router 必须配置base: window.__POWERED_BY_QIANKUN__ ? /order : /否则子应用内部路由会跟主应用路由对不上跳转就会乱。另外如果你的子应用里使用了 Webpack 的 publicPath可以这么做// 子应用 vue.config.js 或 webpack 配置 const packageName require(./package.json).name; module.exports { publicPath: process.env.NODE_ENV development ? //localhost:8081 : /${packageName}/, configureWebpack: { output: { library: packageName, libraryTarget: umd } }, devServer: { port: 8081, headers: { Access-Control-Allow-Origin: * } } };注意devServer.headers这行qiankun 需要跨域加载子应用的 entry如果你不配置允许跨域主应用会加载不到子应用资源。这个细节很多第一次接入的人会漏掉。5.3 完整代码示例props 传参、回传、全局状态下面给一个相对完整的通信闭环示例你可以把它当作模板去套。主应用发送初始数据 监听回传// 主应用 App.vue简化 export default { mounted() { // 监听子应用回传的状态 actions.onGlobalStateChange((state) { if (state.order_refund) { this.refreshBadge(state.order_refund); } }, true); }, methods: { onNavigateToOrder() { history.pushState(null, , /order); } } }子应用使用 props 和回传// 子应用 order 项目 main.js let instance null; let globalProps null; export async function bootstrap() { // 只做一次性的初始化 } export async function mount(props) { globalProps props; const { container } props; instance new Vue({ router, store: initStore(props), render: (h) h(App) }).$mount(container ? container.querySelector(#app) : #app); } export async function unmount() { instance.$destroy(); instance.$el.innerHTML ; instance null; globalProps null; } // 业务代码里统一从这里取通信能力 export function getQiankunProps() { return globalProps; }子应用某个页面回传数据// 子应用订单页 import { getQiankunProps } from /main; function handleRefundSuccess(orderNo) { const props getQiankunProps(); // 方式一直接调用主应用传入的回调 props.emit(refund, { orderNo, time: Date.now() }); // 方式二通过全局状态回传 props.setGlobalState({ order_refund: { orderNo, time: Date.now() } }); }这两种方式我建议根据场景选择如果只是单个主应用模块需要感知用回调如果多个模块比如订单列表、统计报表都要感知同一个业务事件用全局状态。5.4 独立运行和嵌入运行如何共存最后再讲一个很实用的话题子应用怎么做到独立运行不报错、嵌入主应用正常跑。本地开发时子应用开发者往往只想打开自己的页面调试不想被主应用包裹。这时候window.__POWERED_BY_QIANKUN__的判断就派上用场了。独立运行时走正常的前端开发流程比如加载 devServer 的入口 HTML根组件自己挂载嵌入运行时走 qiankun 的装载流程。有一个容易踩的坑独立运行时子应用的接口请求是直接发到后端不存在跨域问题但嵌入到主应用后因为子应用页面域名是主应用的域名接口请求就变成了跨域。这时候你需要在主应用或子应用里做统一的 axios 封装让请求带上完整的前缀并在网关层放开跨域限制。我见过一个项目把接口请求写死成子应用本地地址嵌入主应用后一直请求失败排查了半天才发现是接口地址写死的问题。最好的办法是子应用统一通过环境变量配置接口地址独立运行时读自己 devServer 的代理配置嵌入时读主应用下发的全局接口前缀。这个配置可以在 props 里传给子应用子应用在 mount 时接收后存入本地配置中心之后所有请求都走这个动态配置就不会环境一换就崩。收尾的几点个人体会做完几个微前端项目我自己最强烈的感受是qiankun 把加载这件事做得很顺但真正决定项目长期好不好维护的是生命周期里那几条纪律和通信的数据流设计。不要因为框架提供了一套全局状态就把所有数据都塞进去更不要图省事让子应用之间直接操作 window 上的变量。数据流有多清晰后期踩坑就有多轻松。最后分享一个我一直在用的小技巧在子应用的 mount 和 unmount 里我都会加一段调试日志打印当前 props 和 DOM 容器的状态。上线时可以通过环境变量关掉但开发阶段这些日志能帮你快速判断子应用到底有没有被正确挂载是不是走了独立运行分支比打开 DevTools 一遍遍断点快得多。微前端本身不是一个复杂到学不会的技术但它是那种细节决定成败的方案把生命周期和数据通信这两块地基打扎实后面接多少个子应用都不会慌。
返回列表