知

模块四 · 性能、测试与表达

第 14 / 17 节17 分钟阅读型

性能优化手册:从响应式粒度到首屏

性能问题要先用数据定位,再动手。这一节按"渲染、响应式、加载、运行时"四层给出可验证的优化手段与判断标准。

读完这一节,你能做到

  1. 用 Performance 面板与 Vue DevTools 定位瓶颈。
  2. 按"渲染 → 响应式 → 加载"的顺序选择优化手段。
  3. 判断什么时候该用 shallowRef、v-memo、虚拟滚动。
  4. 用指标验证优化是否真的有效。

一、先定位,再优化

现象可能原因工具
输入卡顿大组件重渲染Vue DevTools 的组件更新时间
首屏慢包体大、请求多Network、Lighthouse
滚动掉帧长列表全量渲染Performance 火焰图
内存上涨定时器/监听未清理Memory 快照

没有测量就没有优化。先记录基线(FCP、LCP、长任务数量),改完再对比。

二、渲染层优化

  1. 拆小组件:把频繁变化的部分隔离,避免整块重渲染。
  2. v-memo:对列表项做记忆化,依赖不变就跳过 diff。
  3. v-once:纯静态内容只渲染一次。
  4. 虚拟滚动:只渲染可视区域,长列表从 O(n) 降到 O(可见数量)。
  5. 稳定 key:避免用下标,减少节点复用错误与多余更新。

三、响应式层优化

  • shallowRef / shallowReactive:只追踪第一层,适合大对象、图表数据。
  • markRaw:标记不需要响应式的对象(如第三方实例),避免代理开销。
  • 避免在模板里写复杂表达式:每次渲染都会重新计算。
  • 用 computed 缓存派生值,而不是在模板里链式调用方法。
  • 大数组不要整体替换,尽量做增量更新。
// 大对象只关心整体替换
const chartData = shallowRef(loadData());
chartData.value = nextData; // 触发更新

四、加载层优化

手段作用注意
路由懒加载按页面拆分 chunk首屏只加载当前页
异步组件按需加载重组件配合 Suspense 处理加载态
预加载空闲时加载下一页不要抢占首屏带宽
图片懒加载减少首屏请求注意占位与布局抖动
依赖分析去掉大包依赖用构建分析器看体积

五、运行时优化

  • 及时清理定时器、事件监听、watch。
  • 避免在 watch 里做重计算,必要时加防抖。
  • 长任务拆分:用 requestIdleCallback 或分片处理。
  • 不要在渲染函数里创建大对象或做深拷贝。

六、验证优化

优化前:记录 FCP、LCP、长任务数、组件渲染次数
只改一个手段
优化后:同样口径对比

常见误区:包体小了但首屏没变快(瓶颈在网络或渲染);组件渲染次数少了但输入仍卡(瓶颈在主线程长任务)。

七、首屏指标的拆解

指标含义优化方向
FCP首次内容绘制减少阻塞资源、内联关键 CSS
LCP最大内容绘制图片/字体/服务端响应
TBT总阻塞时间拆分长任务、减少 JS 执行
CLS布局偏移预留尺寸、避免动态插入

首屏慢先分清是“资源加载慢”还是“主线程忙”。Network 面板看瀑布图,Performance 面板看长任务,两者指向的优化完全不同。

八、渲染性能的测量方法

  • 用 Performance 录制一段交互,找“长任务”与“强制同步布局”。
  • 用 Vue DevTools 的组件渲染时间,定位是哪个组件在反复更新。
  • 在关键组件里打点,统计 render 次数,而不是凭感觉。

不要用“感觉快了”作为结论,至少要有一个可重复的指标。

九、优化优先级

1. 减少不必要的渲染(拆组件、稳定 key、v-memo)
2. 减少每次渲染的工作量(computed、shallowRef)
3. 减少首屏资源(懒加载、分包、图片)
4. 减少主线程长任务(分片、Worker)

先做收益大、风险低的;像“全站 SSR”这种改造,应该在确认瓶颈在首屏渲染后再考虑。

一个判断顺序:如果交互卡顿,先看渲染次数;如果首屏慢,先看资源;如果滚动卡,先看列表规模。三类问题的工具与手段都不同。

性能预算

指标建议目标说明
首屏 JS< 200KB(gzip)超出后解析成本明显上升
LCP< 2.5s图片与字体是主因
长任务< 50ms超过就会阻塞输入
组件渲染单次 < 16ms超过一帧就会掉帧

预算写进 CI 或发布检查,而不是写在文档里。

不要过早优化:没有用户反馈的问题,先不优化;优化前先确认它真的是瓶颈,而不是“看起来可以更快”。

虚拟滚动的三个注意点

  1. 不定高列表要先测量或给估算值,否则滚动条会跳动。
  2. 回收节点时要清理副作用(如图片懒加载监听),否则会泄漏。
  3. 快速滚动时用占位块,避免白屏。

优化的收益要用同一口径对比,否则只是自说自话;先做减法,再做加法。

一个首屏案例

某后台首屏 4.2 秒:路由未分包(1.1MB JS)、首屏请求串行、图表库全量引入。动作:路由懒加载、图表按需引入、首屏请求并行化。结果:首屏 1.8 秒,代价是首屏多了一次加载态。这个案例的关键不是手段,而是先用 Network 面板确认瓶颈在加载,而不是渲染。

优化前后都要记录,否则无法证明有效;指标要可重复。

十、常见坑

  1. 为了性能到处加 v-if,结果逻辑更难维护。
  2. 用 shallowRef 后直接改内部属性,发现不更新。
  3. 虚拟滚动没处理不定高度,滚动跳动。
  4. 懒加载拆得太碎,请求数反而变多。
  5. 只看 Lighthouse 分数,不看真实用户指标。

十一、动手任务:自写 → AI 写 → 对比 → 取舍

四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。

① 自己写

  1. 用一个 1,000 行的列表页,测量滚动帧率,再改成虚拟滚动对比。
  2. 给一个静态区块加 v-once,用 DevTools 看渲染次数变化。
  3. 用构建分析器找出体积最大的三个依赖,评估是否可替换。

② 让 AI 写同一题

把第 2 题(首屏优化)交给 AI,补一句 每项优化都要给优化前后的数字。

③ 对比要盯的点

  • 它先测后改,还是直接上 v-memo、shallowRef、路由懒加载三件套?
  • 它给的数字有出处吗?还是“这样会快很多”。
  • 它有没有把“减少响应式开销”和“减少渲染量”当成同一件事?

④ 一句话取舍

它列的三件套里,在你这页面上哪一件其实是无效的?

十二、自测

  1. 性能优化前必须先做什么?
  2. v-memo 与 computed 的区别是什么?
  3. shallowRef 适合什么场景?
  4. 虚拟滚动为什么能提升长列表性能?
  5. 包体变小但首屏没变快,可能是什么原因?

参考答案要点:测量基线;一个控制渲染、一个缓存值;大对象整体替换;只渲染可视区域;瓶颈在网络/渲染/主线程。

配套题目

★ 越多越该先背;点题目可看完整答案与深入版。

提示:读完可以顺手做几道对应题目,检验是否真的理解