在大型单页应用中,性能瓶颈往往并非 Vue 本身,而是开发者对运行时行为的不当操作。下面列出的七条策略均来自源码级观察与线上验证,每一条都旨在减少不必要的重新渲染、DOM 操作或主线程阻塞。
一、为列表节点提供稳定且唯一的 key
v-for
渲染的节点在没有 key 时,Vue 依赖就地复用策略:相同标签名的元素会被直接复用,可能导致状态错位或过渡动画异常。使用稳定且唯一的 key(通常是数据库主键或业务 ID)能让 diff 算法精确识别"新增 / 移动 / 删除",从而最小化真实 DOM 的变更范围。
注意:随机数或索引不是稳定 key,前者会导致每次渲染都触发插入,后者在数组顺序变化时失去意义。
二、冻结纯展示数据
Object.freeze
会阻止 Vue 把对象递归地转换成响应式。对于一次性渲染、永不修改的只读数据(如字典表、静态配置),在注入 store 或组件前先行冻结,可节省大量 defineReactive
调用与依赖追踪开销。
典型用法:
js
const dict = Object.freeze(await fetchDict())
三、优先使用函数式组件展示无状态片段
函数式组件没有实例 (this
为 null
),也不参与响应式系统。它们只是返回 VNode 的纯函数,渲染开销与普通 VNode 相同,却完全跳过了生命周期、依赖收集与 patch 时的实例比对。
适合场景:列表卡片、纯 UI Badge、Icon 等完全由 props 驱动的展示单元。
四、用计算属性缓存高代价派生值
模板中多次出现的复杂表达式,如果依赖不变,每次渲染都会重新执行。将其提炼为计算属性后,Vue 会为其创建惰性 Watcher,仅当依赖变化时才重新求值,其余时间直接返回缓存结果。
经验法则:若表达式包含循环、正则、深拷贝等 CPU 操作,就值得提升为计算属性。
五、降低输入型组件的实时同步频率
v-model
默认在 input
事件同步,每次键盘敲击都会触发响应式更新,若此时页面存在动画,主线程压力陡增。对不要求实时校验的场景,可使用 .lazy
修饰符改为 change
事件同步,或干脆拆成 :value
+ @change
手动提交,从而把 JS 执行与渲染线程错开,避免动画掉帧。
六、保持对象引用稳定
Vue 的变更检测采用 引用级严格相等。对于对象类型,只要不替换引用,内部属性再深地变化也不会触发父级更新。 利用这一点,可把大型对象按功能拆分为多个子组件,子组件只接收自己关心的引用。父级在引用不变时即可跳过 patch,子组件则按需精准更新。
典型反模式:在父级用展开运算符生成新对象 { ...obj }
,看似方便,实则破坏引用稳定性。
七、v-show 与延迟装载
-
v-if vs v-show:前者在切换时会触发插入 / 删除 DOM 树;后者仅切换
display
。对于内部含大量节点且显示状态频繁变化的区域(折叠面板、标签页切换),v-show 可避免昂贵的树重建。
-
延迟装载:即使所有代码已下载,一次性渲染数百个复杂组件仍会阻塞首帧。
利用
requestAnimationFrame
把渲染任务切片,按优先级或可视区域分批挂载,可显著降低白屏时间。社区常见的
vue-virtual-scroller
、vue-lazy-hydrate
均基于同一思路。