Vue 性能优化策略

发布时间:2026/6/29 23:12:28
Vue 性能优化策略 文章目录前言一、优化原则1.1 先测量再优化1.2 优化层次二、渲染层优化2.1 合理使用 key2.2 v-once静态内容只渲染一次2.3 v-memo条件缓存子树Vue 3.22.4 v-memo vs computed2.5 v-if vs v-show2.6 减少组件嵌套与无效 render三、响应式层优化3.1 shallowRef / shallowReactive3.2 markRaw标记永不代理3.3 减少 reactive 范围3.4 computed 缓存 vs watch四、代码与加载优化4.1 路由懒加载4.2 组件异步加载4.3 Tree-shaking4.4 KeepAlive 缓存页面五、大列表虚拟滚动5.1 问题5.2 方案5.3 配合优化六、其他实用手段6.1 防抖 / 节流6.2 Web Worker6.3 图片与资源6.4 编译层框架内置七、面试聚焦7.1 优化应基于实际测量7.2 shallowRef 的 .value 变化会触发更新吗7.3 v-memo 和 computed 的区别7.4 v-once 和静态提升的区别八、易混淆点九、思考与练习总结前言Vue 3 本身已有编译优化和 Proxy 响应式但业务场景仍可能出现大列表、频繁更新、首屏过慢等问题。本篇从渲染、数据、代码、测量四个层面梳理 Vue 性能优化策略。本篇会讲清楚shallowRef / shallowReactive / markRawv-once / v-memo 的使用与区别大列表虚拟滚动与懒加载优化应基于实际测量一、优化原则1.1 先测量再优化// 不要凭感觉优化// 1. Chrome Performance 面板 → 录制交互看 Long Task、Layout、Paint// 2. Lighthouse → FCP、LCP、TBT// 3. Vue DevTools → 组件 render 次数、耗时原则说明有数据再动手定位瓶颈是 render、网络还是计算避免过早优化简单页面不必堆 v-memo、shallowRef优先低成本懒加载、合理 key 往往比改架构见效快关注用户感知LCP、INP 比纯 JS 执行时间更重要1.2 优化层次编译层Vue 3 内置→ 静态提升、PatchFlag、Block Tree 渲染层 → key、v-once、v-memo、虚拟滚动 响应式层 → shallowRef、markRaw、减少 reactive 范围 代码层 → 路由/组件懒加载、Tree-shaking二、渲染层优化2.1 合理使用 key列表用稳定唯一 id作 key减少错误复用和不必要 DOM 操作详见《Key 的作用与原理》。2.2 v-once静态内容只渲染一次header v-once h1{{ title }}/h1 nav固定导航/nav /header首次渲染后跳过该子树后续所有更新含 props 变化适合页脚、版权、固定说明等几乎不变的内容与编译期静态提升不同v-once 是运行时指令开发者手动标记2.3 v-memo条件缓存子树Vue 3.2div v-foritem in list :keyitem.id v-memo[item.id, item.selected] ExpensiveItem :itemitem / /div依赖数组不变→ 跳过该子树更新含子组件依赖变化→ 正常更新适合大列表中昂贵子组件且只有部分字段影响展示!-- 选中态变化才重渲染该项 -- div v-memo[item.id, item.selected] ItemCard :itemitem / /div2.4 v-memo vs computed对比项v-memocomputed作用位置模板子树script 逻辑跳过整段模板 子组件 render重复计算依赖显式数组[a, b]自动收集v-memo 管「这块模板要不要重绘」computed 管「这个值要不要重算」。2.5 v-if vs v-show场景推荐频繁切换显示v-show只改 display很少出现v-if不渲染 DOM2.6 减少组件嵌套与无效 render不要把大对象整包 reactive 后传给大量子组件用v-memo、拆分组件、Pinia 按需订阅减少无关更新三、响应式层优化3.1 shallowRef / shallowReactiveimport{shallowRef,shallowReactive,triggerRef}fromvue// 只代理 .value 引用变化不深度代理内部constbigDatashallowRef({list:[...10000items]})// ✅ 替换整个对象 → 触发更新bigData.value{list:newList}// ❌ 改深层属性 → 不触发更新bigData.value.list.push(item)// 手动触发bigData.value.list.push(item)triggerRef(bigData)API深度代理触发更新ref是对象深层变化也触发shallowRef否仅.value替换触发reactive是深层变化触发shallowReactive仅第一层仅第一层属性触发场景第三方库实例、大型不可变数据、实时 tick 数据批量替换。3.2 markRaw标记永不代理import{markRaw,reactive}fromvueconstchartmarkRaw(echarts.init(dom))conststatereactive({chart,// 不会被 proxy避免无效依赖option:{}})适合 ECharts、地图 SDK 等不需要响应式的大对象。3.3 减少 reactive 范围// ❌ 整个表单大对象都 reactiveconstformreactive({/* 50 个字段 */})// ✅ 只有会驱动视图的字段 reactiveconstvisibleFieldsreactive({name:,status:})conststaticConfig{/* 只读配置普通对象 */}3.4 computed 缓存 vs watch// ✅ 派生数据用 computed有缓存constfilteredcomputed(()list.value.filter(ii.active))// ✅ watch 默认懒执行避免 immediate 滥用watch(source,handler)// 默认 flush: pre按需触发四、代码与加载优化4.1 路由懒加载{path:/report,component:()import(/views/Report.vue)}按路由拆 chunk减小首屏 JS详见《路由懒加载》。4.2 组件异步加载constHeavyChartdefineAsyncComponent(()import(./HeavyChart.vue))弹窗、Tab 内重型组件按需加载详见《动态组件与异步组件》。4.3 Tree-shaking按需导入import { ref } from vue避免全量工具库import debounce from lodash-es/debounce构建工具生产模式自动 tree-shake4.4 KeepAlive 缓存页面Tab、列表→详情→返回场景缓存实例避免重复创建详见《KeepAlive 缓存组件》。注意max控制内存。五、大列表虚拟滚动5.1 问题渲染 1 万条li→ 1 万个 DOM 节点 → 卡顿、内存高。5.2 方案只渲染可视区域 少量缓冲区的项script setup import { RecycleScroller } from vue-virtual-scroller import vue-virtual-scroller/dist/vue-virtual-scroller.css /script template RecycleScroller :itemslist :item-size50 key-fieldid v-slot{ item } div classrow{{ item.name }}/div /RecycleScroller /template常用库vue-virtual-scroller、tanstack/vue-virtual。5.3 配合优化列表项用v-memo[item.id, item.xxx]项内避免深层 reactive稳定key用业务 id六、其他实用手段6.1 防抖 / 节流搜索输入、scroll、resize 监听使用 debounce/throttle减少 render 和请求次数。6.2 Web Worker大 JSON 解析、复杂计算放 Worker避免阻塞主线程 UI。6.3 图片与资源懒加载loadinglazy、v-lazy 指令合适格式WebP、压缩、CDN路由 prefetch 空闲预加载详见《路由懒加载》6.4 编译层框架内置Vue 3 模板自动静态提升、PatchFlag、Block Tree无需手写详见《编译优化》。手写 render 函数享受不到优先写模板。七、面试聚焦7.1 优化应基于实际测量Performance、Lighthouse、Vue DevTools 定位瓶颈避免盲目 v-memo。7.2 shallowRef 的 .value 变化会触发更新吗会。shallowRef.value newObj触发更新改value内部深层属性不会需triggerRef或整体替换。7.3 v-memo 和 computed 的区别v-memo 跳过模板子树 rendercomputed 缓存计算结果。一个管视图一个管逻辑。7.4 v-once 和静态提升的区别静态提升是编译期自动识别静态节点v-once 是运行时指令手动标记且跳过后续所有更新。八、易混淆点shallowRef 不是不更新换.value会更新只是不深追踪内部。v-once 后 props 变也不更新确认内容真正静态再用。v-memo 依赖要写全漏写依赖会导致该更新不更新。虚拟滚动不减少数据量只减少 DOM 数量接口仍可能要分页。KeepAlive 占内存需max和include控制。九、思考与练习1.大列表卡顿如何优化解析虚拟滚动减少 DOM稳定 keyv-memo 昂贵子项分页或懒加载数据。2.shallowRef 适用场景解析大型对象整体替换、第三方实例、频繁替换 .value 但不关心深层变化的场景。3.v-memo 依赖数组如何写解析列出影响该子树展示的所有响应式值如[item.id, item.status]。4.首屏 JS 过大怎么办解析路由懒加载、异步组件、Tree-shaking、分析 bundlerollup-plugin-visualizer。5.为什么 say「先测量再优化」解析避免无效优化增加复杂度不同瓶颈方案不同网络 vs render vs 计算。总结原则先测量再针对性优化避免过早优化渲染key、v-once、v-memo、v-show/v-if、虚拟滚动响应式shallowRef、markRaw、缩小 reactive 范围、computed 缓存代码路由/组件懒加载、Tree-shaking、KeepAlive本质减少 DOM 数量、减少无效 render、减小首屏体积