模块四 · 性能、测试与表达
性能优化手册:从响应式粒度到首屏
性能问题要先用数据定位,再动手。这一节按"渲染、响应式、加载、运行时"四层给出可验证的优化手段与判断标准。
读完这一节,你能做到
- 用 Performance 面板与 Vue DevTools 定位瓶颈。
- 按"渲染 → 响应式 → 加载"的顺序选择优化手段。
- 判断什么时候该用 shallowRef、v-memo、虚拟滚动。
- 用指标验证优化是否真的有效。
一、先定位,再优化
| 现象 | 可能原因 | 工具 |
|---|---|---|
| 输入卡顿 | 大组件重渲染 | Vue DevTools 的组件更新时间 |
| 首屏慢 | 包体大、请求多 | Network、Lighthouse |
| 滚动掉帧 | 长列表全量渲染 | Performance 火焰图 |
| 内存上涨 | 定时器/监听未清理 | Memory 快照 |
没有测量就没有优化。先记录基线(FCP、LCP、长任务数量),改完再对比。
二、渲染层优化
- 拆小组件:把频繁变化的部分隔离,避免整块重渲染。
v-memo:对列表项做记忆化,依赖不变就跳过 diff。v-once:纯静态内容只渲染一次。- 虚拟滚动:只渲染可视区域,长列表从 O(n) 降到 O(可见数量)。
- 稳定 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 或发布检查,而不是写在文档里。
不要过早优化:没有用户反馈的问题,先不优化;优化前先确认它真的是瓶颈,而不是“看起来可以更快”。
虚拟滚动的三个注意点
- 不定高列表要先测量或给估算值,否则滚动条会跳动。
- 回收节点时要清理副作用(如图片懒加载监听),否则会泄漏。
- 快速滚动时用占位块,避免白屏。
优化的收益要用同一口径对比,否则只是自说自话;先做减法,再做加法。
一个首屏案例
某后台首屏 4.2 秒:路由未分包(1.1MB JS)、首屏请求串行、图表库全量引入。动作:路由懒加载、图表按需引入、首屏请求并行化。结果:首屏 1.8 秒,代价是首屏多了一次加载态。这个案例的关键不是手段,而是先用 Network 面板确认瓶颈在加载,而不是渲染。
优化前后都要记录,否则无法证明有效;指标要可重复。
十、常见坑
- 为了性能到处加
v-if,结果逻辑更难维护。 - 用
shallowRef后直接改内部属性,发现不更新。 - 虚拟滚动没处理不定高度,滚动跳动。
- 懒加载拆得太碎,请求数反而变多。
- 只看 Lighthouse 分数,不看真实用户指标。
十一、动手任务:自写 → AI 写 → 对比 → 取舍
四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。
① 自己写
- 用一个 1,000 行的列表页,测量滚动帧率,再改成虚拟滚动对比。
- 给一个静态区块加
v-once,用 DevTools 看渲染次数变化。 - 用构建分析器找出体积最大的三个依赖,评估是否可替换。
② 让 AI 写同一题
把第 2 题(首屏优化)交给 AI,补一句 每项优化都要给优化前后的数字。
③ 对比要盯的点
- 它先测后改,还是直接上
v-memo、shallowRef、路由懒加载三件套? - 它给的数字有出处吗?还是“这样会快很多”。
- 它有没有把“减少响应式开销”和“减少渲染量”当成同一件事?
④ 一句话取舍
它列的三件套里,在你这页面上哪一件其实是无效的?
十二、自测
- 性能优化前必须先做什么?
v-memo与computed的区别是什么?shallowRef适合什么场景?- 虚拟滚动为什么能提升长列表性能?
- 包体变小但首屏没变快,可能是什么原因?
参考答案要点:测量基线;一个控制渲染、一个缓存值;大对象整体替换;只渲染可视区域;瓶颈在网络/渲染/主线程。
配套题目
★ 越多越该先背;点题目可看完整答案与深入版。
提示:读完可以顺手做几道对应题目,检验是否真的理解