React与Vue3在真实项目中的渲染性能对比实测

打工人秀丽 框架 阅读 544
赞 36 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

性能对比这事,我干了快五年,从 Vue 2 到 React 18,再到最近折腾的 Solid 和 Qwik,踩过最多坑的地方不是框架语法,而是「怎么比才不算自欺欺人」。很多人一上来就跑 Lighthouse、开 DevTools 的 Performance 面板,记下几个数字发个 PR 说「优化了 300ms」——结果上线后用户反馈「怎么更卡了?」

React与Vue3在真实项目中的渲染性能对比实测

我现在的做法很简单:不比「峰值」,只比「用户能感知的」;不比「单次渲染」,而比「连续交互下的稳定性」;不比「本地 localhost」,一定要上真实环境压测。

比如最近一个后台表格组件的性能对比,我用的是这套最小闭环方案:

// 性能对比脚本(直接塞在组件 mounted / useEffect 里)
const startMark = performance.now();
const startMemory = performance.memory?.usedJSHeapSize || 0;

// 模拟用户操作:加载 500 条数据 + 渲染 + 点击排序 + 滚动到第 200 行
await loadData(500);
await nextTick(); // Vue 或 React 的 flush sync
await sortData('name');
await nextTick();
await scrollToRow(200);

const endMark = performance.now();
const endMemory = performance.memory?.usedJSHeapSize || 0;

console.log(【性能对比】总耗时: ${endMark - startMark}ms, 内存增长: ${(endMemory - startMemory) / 1024 / 1024}MB);

注意:这个脚本我从来不在生产环境运行,只在本地 dev 模式 + Chrome 无痕窗口 + 禁用所有插件下执行。而且每次对比前,我都会手动清空浏览器缓存 + 关掉所有其他标签页 —— 这个细节我踩过三次坑,有一次对比结果差了 400ms,就因为后台开着 Slack 和 Notion。

为什么不用 Lighthouse?它默认跑的是「冷启动首屏」,而我们业务里 90% 的卡顿都发生在「点了三次筛选、切了四次 Tab、再点开弹窗」之后。Lighthouse 测不出来,但用户手指会记住。

这几种错误写法,别再踩坑了

下面这几个反面案例,都是我团队里真实 PR 里见过的,有的我亲手改过,有的我 review 时当场叫停:

  • 用 console.time() 包整个组件挂载:比如 console.time('TableMount') 放在 setup() 开头,console.timeEnd('TableMount') 放在 return 前。错在哪?它把 async 请求、computed 计算、watch 响应全算进去了,但这些根本不是渲染瓶颈。你优化了 computed,时间没变,因为真正卡的是虚拟滚动 diff。
  • 只测 Chrome,不看 Safari:有一次我们发现某个列表在 Chrome 下 80ms 渲染完,在 Safari 下要 320ms。查了半天,是用了 new Intl.NumberFormat() 做千分位格式化 —— Chrome 缓存强,Safari 每次都新建实例。改成复用 formatter 实例后,Safari 直接降到 95ms。现在我本地必开 Safari 技术预览版跑一遍。
  • 用 mock 数据测性能:mock 返回的是浅层对象 { id: 1, name: 'a' },但真实接口返回的是嵌套 5 层、带 getter、有 Symbol key 的对象。结果 mock 下 60fps,真数据一上直接掉帧。我现在强制要求:性能测试必须连真实后端(哪怕只连 jztheme.com/api/mock-table-data),或者用 JSON.stringify + parse 模拟深克隆后的结构。
  • 忽略 CSS 引起的重排:有个同事优化了 JS 渲染逻辑,把 render 时间压到 12ms,但忘了表格列宽是靠 JS 动态计算后写 inline style 的。每次 resize 都触发 layout,DevTools 里 Layout 耗时飙升到 200ms+。后来改成用 CSS container queries + flex,JS 完全不碰 width,layout 时间归零。

实际项目中的坑

最让我头疼的不是技术,是协作流程。

我们有个「性能基线」制度:每个新功能上线前,必须提交一份 perf-baseline.md,里面包含三组数据:当前主干分支的基准值、PR 分支的实测值、差异说明(必须写清楚「为什么这个改动会导致内存+2MB」)。但第一次推这个制度时,70% 的 PR 直接没填,或者随便写个「优化了渲染」。

后来我干了件很土的事:在 CI 里加了个检查脚本,如果 PR 没提交 perf-baseline.md,CI 就 fail,并输出一行提示:

请先在本地跑一次 performance/benchmark.js,把结果贴进 perf-baseline.md。别跳过,上次跳过的 PR 导致线上 table 卡顿,回滚花了 2 小时。

效果立竿见影。不是靠教育,是靠「不填就过不了 CI」的物理限制。

还有个血泪教训:千万别信「打包体积小 = 运行快」。我们有个组件,用 esbuild 打包后只有 12KB,但里面用了大量 Proxy + Reflect 构建响应式,iOS 15 下首次渲染卡顿严重。最后换成手写的 defineProperty 响应式(代码量翻倍,体积涨到 28KB),帧率反而稳了。体积和性能没有线性关系,这点一定要清醒。

最后补一句实在话

性能对比没有银弹。我试过自动化录制用户操作流来做对比,也试过用 Puppeteer 启动多个 Chrome 实例压测,最后都放弃了 —— 太重,没人愿意维护。现在我就是老老实实写上面那个 10 行 JS 脚本,每次改一点,跑三遍取平均值,截图发到群里。够用,且所有人都看得懂。

如果你有更好的轻量级对比方案,比如怎么自动化抓取「用户真实操作路径」做回归对比,欢迎评论区甩链接。我最近也在看 WebPageTest 的 API,想把它集成进 pre-commit,但还没动手 —— 主要是懒,也怕搞复杂了大家更不愿意填 baseline。

以上是我踩坑后的总结,希望对你有帮助。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论