拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Vue3 核心原理与工程实践全攻略:从响应式到动态路由排查

2026年了Vue3 早就不再是“新框架”但我在团队内部做代码评审和帮人看面试题时发现很多人对 Vue3 的掌握其实还停留在“会用 script setup 写组件”的阶段。响应式原理说不清、动态路由配不明白、富文本一接就报错、Edge 上出个怪问题排查半天——这些都是真实项目里反复出现的点。这篇是 2026 年 3 月我做的一次知识总结把 Vue3 从核心原理到工程实践、再到高频故障排查串了一遍。目标读者是两类人一是准备跳槽面试、需要把 Vue2 和 Vue3 差异讲清楚的同学二是已经在用 Vue3 写后台管理系统但遇到具体功能不知道选什么方案、出了问题不知道从哪里下手的开发者。内容不算进阶但覆盖很全基本可以当手册查。1. 先搞懂 Vue3 最核心的变化响应式与组合式 API很多面试题的第一题就是“Vue3 的响应式原理”但实际业务里真正影响代码质量的往往是响应式 API 的用法细节。这两个东西必须一起理解。1.1 响应式系统Proxy 如何解决 defineProperty 的三大痛点Vue2 用 Object.defineProperty 做数据劫持它有三个天然痛点第一它只能劫持对象已有的属性。如果你在 data 里没有预先声明某个属性后期直接this.obj.newKey value这个新属性不会是响应式的。所以 Vue2 才被迫提供$set和$delete。第二数组的原生操作劫持不了。Vue2 通过重写 push、pop、shift、unshift、splice、sort、reverse 这七个方法来模拟但通过下标赋值arr[0] xxx依然是盲区。第三初始化时必须递归遍历整个对象把每一层属性都转成 getter/setter。对象越深初始化开销越大而且这个遍历是一次性的后面新增的属性也不会自动变成响应式。Vue3 直接用 ES6 的 Proxy 包装整个对象解决了以上三个问题。Proxy 拦截的是对象的任何操作包括属性新增、删除、in 操作符、遍历数组下标和 length 的变更也全部能被拦截到。底层依赖收集的原理其实没变读取属性时收集依赖修改属性时触发更新差别只是拦截层从“属性级”变成了“对象级”。提示Vue3 中依然存在响应式丢失的问题。原因不是 Proxy 不行而是你把响应式对象解构赋值给普通变量或者传给函数参数时断掉了引用链。在业务代码里常见错误是把 reactive 对象解构出来用比如const { name } state这样 name 就是一个普通字符串。推荐用toRefs或toRef处理。1.2 setup 时代ref 和 reactive 到底该选谁ref和reactive的用法差异是新手最容易栽跟头的地方。我的建议很简单基础类型string、number、boolean、bigint用ref对象、数组用reactive也行但在泛型逻辑、传参、返回、解构场景下一律ref更省心为什么因为ref在底层其实也调用了reactive它把.value指向的那个对象代理了一遍本质上是对 reactive 的二次包装。用ref传参时你始终传的是带.value的引用对象传递过程中不会丢响应性而reactive对象解构会丢。尤其是从组合式函数返回数据时如果返回一个 reactive 对象使用方一旦解构就得用 toRefs不熟悉的人很容易踩坑。还有一个细节很多人不知道ref对象直接放进reactive里会自动解包但reactive对象放进ref里不会继续深层包装你拿到的.value依旧是那个 reactive 代理。这个行为在源码层有明确区分业务上你只要记住不要搞混合嵌套要么全用 ref要么结构清晰地区分不然调试时看控制台会非常迷惑。我在项目里定的规范是全组件统一用ref声明业务数据再配合解构赋值保持代码简洁真正用起来反而最少踩坑。1.3 computed、watch 的依赖追踪与常见细节computed在 Vue3 里有两个容易被忽略的点一是缓存二是只有在渲染函数里被访问时才会触发求值。如果你在 setup 里只定义了 computed 但没有任何地方读取它它不会反复执行也不会无意义地消耗性能。这个行为叫“惰性求值”面试时能提到这个细节会加分。watch和watchEffect的区别也要说清楚watch需要明确指定监听源拿到的回调里可以分别拿到新值和旧值适合“前后对比”场景watchEffect是自动收集依赖你只要在回调里读取了某些响应式变量这些变量变化后回调就会自动重跑适合“副作用”场景比如根据数据变化发请求或者写 localStorage。实际开发里有个高频场景监听路由参数变化重新拉数据。用watch(() route.params.id, handler)这种方式是标准解法。千万不要在onMounted里只拉一次数据否则从详情页 A 切到详情页 B你看到的还是上一次的数据。另外watch支持配置{ deep: true }来深度监听但出于性能考虑尽量把监听目标定到具体的字段不要一上来就 deep 整个对象。2. Vue2 到 Vue3面试常考的差异点与迁移经验Vue2 和 Vue3 的差异是面试题里最吃量的部分但同时也是实际迁移中必须面对的问题。我把变化按“影响业务代码的程度”分成三层。2.1 模板与 API 层面的破坏性变更第一层是直接影响你写模板的改动v-model从单个变成了可以绑定多个。Vue2 的v-model等价于:valueinputVue3 里默认变成了:modelValueupdate:modelValue并且支持v-model:title、v-model:content这种多属性绑定做表单组件时特别方便。.sync修饰符被移除了统一用v-model:propName语法替代。迁移时把update:propName事件名保持住就行。filter过滤器被移除了。团队代码里如果还留着{{ text | formatMoney }}Vue3 下直接编译报错。替代方案要么写到 computed 里要么抽成工具函数在 setup 中引入。自定义指令的钩子名称全变了。bind变成了beforeMountinserted变成了mountedupdate和componentUpdated合并成了updatedunbind变成了unmounted。迁移时对照官方映射表改一遍就行。全局 API 从Vue.use()、Vue.component()变成了app.use()、app.component()事件总线$on、$off、$once被彻底移除常见的跨组件通信推荐用mitt或直接上状态管理。2.2 状态管理生态的悄然替换Pinia 与 useXxxVuex 4 虽然支持 Vue3但在新项目里已经很少有人用了。Pinia 不仅有更简洁的 API还天然契合组合式 API 的风格。对比一下感受更明显维度Vuex 4Pinia定义方式state / mutations / actions / gettersdefineStore直接写 setup 风格修改状态必须走 mutation直接赋值或调 action 均可TypeScript支持但类型推导较繁琐类型推导好模块化需要 modules 嵌套天然按需拆分无嵌套概念Pinia 的defineStore配合 setup 风格可以这样写import { defineStore } from pinia import { ref } from vue export const useUserStore defineStore(user, () { const token ref() const roles refstring[]([]) function setToken(value: string) { token.value value } return { token, roles, setToken } })在组件里用的时候storeToRefs负责把 store 里的响应式数据安全解构出来action 直接调用就行。我见过不少人直接在组件里const { token } useUserStore()这样 token 解出来后就废了必须用storeToRefs。这个细节面试官一问一个准。2.3 生命周期映射与新钩子Vue3 的组件生命周期在迁移时几乎是一一对应改名但有几个新增钩子要注意Vue2Vue3beforeCreate建议去掉逻辑挪进 setupcreated建议直接写 setup 顶层beforeMountonBeforeMountmountedonMountedbeforeDestroyonBeforeUnmountdestroyedonUnmounted注意onActivated和onDeactivated是给keep-alive缓存组件用的。如果你的页面用了 keep-alivemounted 只在第一次进入时执行后面从缓存里恢复时触发的是onActivated。在后台管理系统里凡是“从详情页返回列表页要刷新表格”的需求本质上都是没处理好这个钩子。最佳做法是列表数据请求放在onActivated里而不是onMounted里。3. 工程化落地从 Vite 搭建到后台管理系统Vue3 的项目搭建已经比 Vue2 时代简单太多但工程化配置的坑并不少尤其是后台管理系统这种长期维护的项目选型和配置直接决定后面半年过得舒不舒服。3.1 用 Vite 快速创建项目给新项目配一套方案创建项目基本就是一行命令npm create vitelatest my-admin -- --template vue-ts这里我强烈建议直接选vue-ts模板。虽然团队里可能有人对 TypeScript 有抵触但后台管理系统的接口数据结构复杂类型推导带来的收益远大于学习成本。2026 年的组件库和 UI 生态对 TS 的支持已经非常成熟不用 TS 反而是给自己增加维护负担。创建完项目后有几个配置我会第一时间加上// vite.config.ts import { fileURLToPath, URL } from node:url import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })unplugin-auto-import和unplugin-vue-components是必须装的。它们能让你不用手动import { ref } from vue和import ElButton from element-plus自动引入且按需打包体积和开发体验都能兼顾。在 VSCode 里装好对应的插件后这些自动引入的 API 也能有类型提示。还有一点容易忽略Vite 开发环境下import.meta.env可以拿到环境变量.env.local、.env.production这些文件里定义的VITE_开头的变量会自动注入。注意只有VITE_前缀的变量会暴露到客户端代码里这是安全设计不是 bug。提示Vite 做懒加载分包时动态import()的路径在 Windows 和 Linux 下大小写敏感性不同如果你在 Windows 上开发时没报错部署到 Linux 上线后可能白屏这类问题十有八九是路径大小写不一致排查优先级要排在开发工具之前。3.2 动态路由与权限控制addRoute 加自定义指令后台管理系统最核心的工程化需求就是权限控制。热搜词里“根据权限显示不同首页”“动态路由”都是这个问题。标准做法是登录成功后拿 token再拉取当前用户角色和菜单权限前端根据权限动态注册路由。Vue Router 4 提供router.addRoute()可以在运行时往路由表里追加路由。核心逻辑如下import router from /router interface RouteItem { path: string name: string component: string meta?: { title: string; icon?: string } children?: RouteItem[] } export function setupDynamicRoutes(menus: RouteItem[]) { const modules import.meta.glob(../views/**/*.vue) function mapRoutes(items: RouteItem[], parentPath ) { items.forEach((item) { const fullPath parentPath item.path if (item.component) { const loader modules[../views/${item.component}.vue] const routeRecord { path: item.path, name: item.name, component: loader, meta: item.meta } router.addRoute(routeRecord) } if (item.children item.children.length) { mapRoutes(item.children, fullPath) } }) } mapRoutes(menus) }这里的关键点是import.meta.glob它能做目录级懒加载。注意 component 字段的路径必须和 views 目录结构一致否则这里静默失败页面白屏且控制台只报一个找不到模块的 warning。按钮级权限一般用自定义指令v-permission实现。注册全局指令时指令的mounted里检查用户权限列表没有权限就直接移出当前元素// src/directives/permission.ts import type { Directive } from vue import { useUserStore } from /stores/user export const permission: DirectiveHTMLElement, string | undefined { mounted(el, binding) { const userStore useUserStore() const required binding.value if (!required) return const hasPermission userStore.roles.includes(required) if (!hasPermission) { el.parentNode?.removeChild(el) } } }路由守卫里加拦截逻辑router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })一定记得区分“静态路由”和“动态路由”。首页、登录页、404 页适合静态注册业务页面全部走后端返回的菜单数据动态注册。如果一上来就把所有路由写死那“权限显示不同首页”就无从谈起。3.3 后台模板选型vue-pure-admin、vue-vben-admin、jeecg-b 怎么选热搜词里有“管理系统模板比较”我把当前常见的三个开源模板对比一下帮大家省点调研时间模板技术栈侧重点适合场景vue-pure-admin轻量、TS、强调纯净简单中小后台、需要快速交付vue-vben-admin功能全面、封装较多内置权限、多标签页、Mock 体系中大型系统、有定制空间jeecg-boot前后端一体后端基于 Java自带代码生成器想要低代码形态、需要后端配套选择上我的经验是如果团队以纯前端为主、项目规模不大vue-pure-admin 的起点更低代码好读改起来不费劲如果业务复杂度高需要完整的权限模型和多租户vue-vben-admin 的封装能省很多事但学习曲线也陡如果你们其实需要一个完整的产品形态而不是纯前端模板那 jeecg-b 这类前后端一体的方案更对口它内置的表单设计和报表工具对非研发人员也很友好。这里说的不是“哪个最好”而是“哪个最适合你们当前的阶段”。方案切换的成本很高前期调研花三天下决定比用了一半再推翻重来要划算。3.4 国际化、状态保持与公共布局技巧国际化在 Vue3 项目里基本就是 vue-i18n 的配置问题。Vue3 环境下要特别注意legacy: false这个选项它决定了你用组合式风格的useI18n还是旧版的全局$timport { createI18n } from vue-i18n const i18n createI18n({ legacy: false, locale: localStorage.getItem(locale) || zh-CN, messages: { zh-CN: { common: { confirm: 确认 } }, en-US: { common: { confirm: Confirm } } } })legacy: false配合 Composition API 使用时在组件里要这样拿翻译函数import { useI18n } from vue-i18n const { t, locale } useI18n()注意不要在组件 setup 顶层直接用$t那是 Options API 的写法。切换语言就是给locale.value赋值同时记得把当前语言写入 localStorage否则刷新就丢了。“保存状态栏状态”这个需求如果你指的是页面滚动位置、搜索条件、tabs 激活项这类 UI 状态的恢复核心是两件事一是用 key 给路由视图做精确标记二是配合 keep-alive 缓存组件状态在onActivated里恢复在onDeactivated里保存。如果你指的是浏览器底部状态栏或者原生 App 接过来的 WebView 状态栏高度那是 CSS 环境变量和布局适配的问题注意区分。另外热搜里“在芋道系统中 vue3 表单中 router useRouter() 的写法”其实就是很标准的流程在表单页通过useRouter()拿路由实例提交成功后用router.push(/list)回列表。核心点是在异步回调里使用路由跳转提交按钮要加 loading 防重复点击失败时要停留在当前页并给出提示。逻辑本身不复杂重点是不要丢了错误分支的处理。4. 常用功能实现速查富文本、截图、词云、地图与文档导出后台管理系统做久了你会发现业务需求高度雷同。富文本、截图、词云、流程图、导出 Word、地图每一样都有现成方案但选型和接入细节里藏了不少坑。4.1 富文本与截图别再硬啃旧库Vue2 时期大家喜欢用 vue-quill-editor但它在 Vue3 生态里基本停止维护了。新项目接富文本我的建议是直接选 Quill 2.x 自己封装或者用 wangEditor 5。封装富文本组件的核心思路是同步值和 change 事件template div refeditorRef classeditor/div /template script setup langts import { onMounted, ref, watch } from vue import Quill from quill import quill/dist/quill.snow.css const props defineProps{ modelValue: string }() const emit defineEmits{ (e: update:modelValue, value: string): void }() const editorRef refHTMLElement() let quill: Quill | null null onMounted(() { quill new Quill(editorRef.value!, { theme: snow }) quill.on(text-change, () { emit(update:modelValue, quill!.root.innerHTML) }) }) watch(() props.modelValue, (val) { if (quill val ! quill.root.innerHTML) { quill.root.innerHTML val } }) /script注意watch里必须加val ! quill.root.innerHTML的判断否则输入时又触发赋值会造成光标跳动。这个坑我当年浪费了整整一个下午。截图功能的主流方案是html2canvas和html-to-image。后者基于 SVG 序列化处理跨域图片时更顺手但前者兼容性更老道。大图截取注意设置scale参数和backgroundColor不然默认背景是透明的导出 PNG 在部分浏览器里会变黑。调用截图时先await document.fonts.ready等字体加载完成再截否则输出文字位置会错乱。4.2 词云不规则排布与流程图组件的选型词云需求在数据可视化项目里很常见。Vue3 里最方便的组合是 echarts echarts-wordcloud。不规则排布其实不是“随机”而是通过maskImage传入一张形状图词云会尽量填充到这个形状内部产生你想要的轮廓效果。核心配置import * as echarts from echarts import echarts-wordcloud const chart echarts.init(container.value) chart.setOption({ series: [{ type: wordCloud, shape: circle, maskImage: maskImg, left: center, top: center, width: 90%, height: 90%, sizeRange: [14, 60], rotationRange: [-90, 90], rotationStep: 45, gridSize: 8, drawOutOfBound: false, textStyle: { fontFamily: sans-serif, color: function () { return rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}) } }, data: wordList }] })注意maskImage需要先加载图片再初始化图表否则排布会退化成纯圆形或矩形。另外 ecahrts-wordcloud 插件对 echarts 5 的兼容性需要确认版本装之前先看 peerDependencies。流程图组件如果你只需要画节点和连线推荐vue-flow/core或者 AntV X6如果需要完整的流程编排能力和自定义节点LogicFlow 更合适。选型时先问清楚需求边界只是展示还是能拖拽编辑节点数量级是多少是否需要自动布局这三个问题问完方案基本就定了。Vue2 时代的vue-drag-resize在 Vue3 里可以考虑 v-drag-resize 或直接基于 pointer 事件自己实现拖拽类需求自己写也不难核心是搞清楚元素坐标变化。4.3 导出 Word 与高德地图集成导出 Word 是我见得最多的“临时需求”之一。方案选择取决于你对文档格式的控制要求只是把表格、图片、简单文本导出成 doc用docxtemplater最稳。它本质是模板引擎你把后端返回的数据填进一个预制的 .docx 模板里生成的文件格式最干净。如果前端要拼 HTML 再转 Word可以用html-docx-js或直接把 HTML 片段嵌入 Word 兼容格式中但这种方式在不同 Office 版本里表现不稳定图片处理尤其麻烦不推荐在正式系统里用。如果只是导出 Excel 报表直接用exceljs或xlsx注意合并单元格和样式的性能。高德地图接入 Vue3 的官方推荐方式是amap/amap-jsapi-loader它负责异步加载 AMap 脚本配合onMounted初始化实例import AMapLoader from amap/amap-jsapi-loader let map: any null AMapLoader.load({ key: 你的Key, version: 2.0, plugins: [AMap.Scale, AMap.ToolBar] }).then((AMap) { map new AMap.Map(container.value, { zoom: 12, center: [116.397428, 39.90923] }) })组件卸载时记得map.destroy()否则多次进入页面会累积多个地图实例内存直线上升。4.4 JSX 场景、自定义指令与 Dialog 的现代用法Vue3 使用 JSX 属于“默认支持但需要配置”的能力。用 Vite 建项目时需要加vitejs/plugin-vue-jsx插件然后就能写.tsx文件了。但我的建议是能用模板就用模板模板的静态优化、类型推导更好。JSX 适合两类场景一是高动态渲染比如函数式组件里大量条件分支、递归渲染二是你从 React 转过来、对 render 函数更熟悉用 JSX 过渡会平滑一些。自定义指令在 Vue3 里不仅能操作 DOM还能配合组合式函数处理复杂逻辑。除了权限指令v-permission我平时用得比较多的还有v-debounce防抖提交、v-focus自动聚焦、v-infinite滚动加载。实现方式就是在 directive 对象里写业务逻辑注意清理副作用比如在unmounted里移除事件监听器。Vant 4 的 Dialog 在 Vue3 项目里推荐用函数式调用import { showDialog, showConfirmDialog } from vant async function handleDelete() { await showConfirmDialog({ title: 删除确认, message: 是否确认删除这条数据 }) // 用户点了确认这里继续执行删除逻辑 }函数式调用的好处是不用在 template 里维护 showFlag也不用关心销毁时机代码更集中。如果你是组件式调用van-dialog记得用v-model:show控制显隐并且关闭事件要手动把 show 置为 false。5. 高频问题排查实录来自真实项目的 6 个坑最后这部分是我从真实项目和社区问题里整理的排查经验每一条都有具体场景也会给出排查路径但不确保万能毕竟实际环境千差万别。5.1 Edge 浏览器里浏览器按钮疑似失灵的排查思路“vue3 项目在 Edge 浏览器中有时候无法关闭浏览器右上角的最小化按钮”这个描述我在热搜里看到过实际遇到时多半不是按钮本身的问题而是页面把浏览器主线程卡死了。常见诱因包括页面里有死循环或超大数据渲染。排查方式打开 DevTools Performance录制操作过程看主线程占用率是否接近 100%。无限发请求。如果某个 effect 没有正确清理依赖会导致循环请求最终把浏览器拖到无法响应。页面调用了 Fullscreen API。代码里如果执行了document.documentElement.requestFullscreen()浏览器会进入全屏模式窗口按钮的位置和可用性会受影响。搜一下代码里有没有 fullscreen 相关调用。Edge 扩展干扰或系统输入法相关问题。可以试一下 Edge 的 InPrivate 窗口是否复现如果不复现就基本确定是扩展导致的。这个问题的核心点在于“Edge 特有”所以优先考虑浏览器环境和页面资源的交互而不是 Vue3 自身的 bug。Vue3 能做的事是保证页面主线程不因我们的代码被卡死比如列表用虚拟滚动、大数据用 worker 处理。5.2 路由跳转页面不刷新的真相“路由跳转不刷新页面”是 Vue Router 的一个经典问题。最常见的原因从/detail/1跳到/detail/2组件实例被复用了onMounted不会重新执行所以页面数据不更新。解决方案有三层第一层在组件里用watch(() route.params.id, reload)监听参数变化重新拉数据。这是推荐做法最大程度保留组件内部状态。第二层在router-view上加:keyroute.fullPath强制每次路径变化都重新创建组件。这个方法简单粗暴但会丢失组件内部状态适合详情页这种无所谓状态的场景。第三层用路由钩子onBeforeRouteUpdate做拦截处理。在微前端场景下Vue2 主应用 Vue3 子应用的路由异常多半是子应用实例被重复创建、路由实例没有做销毁清理导致的。检查子应用生命周期里的unmount钩子把 router、app 实例都置空。另外如果用的是 history 路由子应用必须配置正确的base否则刷新页面时资源路径全错表现为白屏或 404。5.3 tabs 标签样式如何“优雅”地改改 Element Plus / Ant Design Vue 的 tabs 样式是后台系统的日常需求。如果你只改当前页面用:deep()选择器就行style scoped .my-tabs :deep(.el-tabs__item) { height: 36px; line-height: 36px; } .my-tabs :deep(.el-tabs__item.is-active) { color: #409eff; font-weight: 600; } .my-tabs :deep(.el-tabs__active-bar) { height: 3px; border-radius: 2px; } /style注意:deep()前面必须写一个能定位到当前组件 DOM 的类名否则会把全局所有 tabs 都改了。这是个很常见的失误写 scoped 样式时以为加个:deep()就不会污染全局但作用域其实还是全局的只是编译加了 hash 属性选择器。如果要全局统一修改 tabs 风格建议直接用 CSS 变量覆盖组件库的 token而不是去改源码。5.4 强制更新组件的几种自救方式Vue3 里基本不需要$forceUpdate因为响应式系统本身够用。如果你觉得数据改了但视图没更新优先检查是否修改了响应式对象的“新增属性”而没有重新赋值。reactive对象新增属性是可以被追踪的但如果你给ref对象里的对象新增属性且这个对象本身没有用reactive包裹就可能不敏感。是否修改了数组的 index。Vue3 支持arr[0] xxx触发更新但如果你直接用length 0清空数组部分浏览器行为不一致建议用arr.splice(0)。真正的“强制更新”场景我用得最多的是通过给组件变更 key 值来强制整树重建。比如某个第三方组件内部状态混乱、重置 API 不管用时在父组件维护一个renderKey每次需要重置就renderKey然后把:keyrenderKey传给子组件。这个方法简单可靠副作用是组件内所有状态丢失使用时想清楚。5.5 监听 scrollHeight 的正确姿势监听 DOM 的 scrollHeight 变化直接在scroll事件里读取是常见做法但如果列表内容是通过异步渲染或图片加载引起的scroll 事件触发时机往往滞后。更可靠的办法是配合ResizeObserver观察容器尺寸变化const container refHTMLElement() onMounted(() { const observer new ResizeObserver(() { const h container.value?.scrollHeight ?? 0 // 根据高度变化做业务处理 }) observer.observe(container.value!) onBeforeUnmount(() observer.disconnect()) })ResizeObserver能捕获元素内容尺寸变化对懒加载、图片加载、异步插入 DOM 都适用。如果你要监听的是整个 document 的高度变化用document.documentElement.scrollHeight配合MutationObserver监听 body 子节点变化也可以但注意 MutationObserver 回调会比较频繁要做节流。5.6 微前端下的路由异常沙箱与 base 问题微前端在 Vue3 项目里绕不开 qiankun 或 wujie。热搜词里有“Vue3 和 React 通过微前端沙箱机制”的描述这里我补充一个排查重点样式隔离。qiankun 的 JS 沙箱通过 Proxy 拦截全局变量的读写样式隔离通过给子应用容器包裹 shadow DOM 或动态处理样式表。如果你发现子应用样式错乱或路由跳转失效先确认子应用里有没有把全局样式直接写在index.html或使用不受控制的 CSS 变量。另外 history 路由下子应用必须显式设置base为子应用挂载路径否则刷新时请求的资源路径全部指向主应用域名页面直接 404。用 wujie 的话它的无界方案对样式隔离处理得更好但对 Web Components 的兼容性有一定要求。我的建议是新项目优先考虑 wujie老项目继续用 qiankun 也完全可以关键是路由的 base 配置和子应用实例的销毁逻辑一定要写对。还有一点子应用的createApp实例一定要在unmount时执行app.unmount()并清除全局事件监听否则内存泄漏和重复渲染跑不掉。最后分享两个小技巧我个人在实际项目里吃了不少亏后养成了两个习惯一个是所有涉及第三方库接入的组件都单独封装一层不直接在业务代码里调二方库 API这样库升级时只需要改封装层业务不用动另一个是给所有异步操作统一封装 loading 和错误处理表单提交、列表请求、导出、下载都不例外虽然前期代码多写一点但线上排查问题时真的省心。这次 Vue3 的知识点总结基本覆盖了从原理到工程的常见问题。如果你正在准备面试建议把第 2 部分的差异点背熟再把第 3 部分的动态路由和第 5 部分的问题排查思路用自己的话说一遍面试官会认为你是真的有实战经验而不是只会刷题。如果后续遇到新的奇葩问题建议先看一下控制台报错、再定位到具体机制层通常就能找到答案。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门