React/Vue/Svelte 选型对比:同一个后台表格页我写了三遍,说点 benchmark 不会告诉你的事

🔑 关键词:前端框架对比, React, Vue, Svelte, 响应式原理

📖 摘要:把同一个 800 行可编辑表格分别用 React 18、Vue 3.4、Svelte 5 实现一遍后,聊聊三种框架在「重渲染边界归谁管」这件事上的根本分歧,以及选型时真正该看的那些指标。

2022 年我接了个内部 CRM 的活,需求听着很简单:一个 60 列、能横向滚动、每行带可编辑输入框的表格,数据量大概 800 行。我先拿 React 写的,第一版丢给测试,人家在搜索框里敲了三个字,页面卡了 1.2 秒。打开 Performance 面板一看,每次 keydown 触发的 render 直接覆盖了整棵 Table 树,800 行乘 60 个单元格,十几个子组件全在重新执行。这当然不是 React 的错,是我没划清楚边界——但我当时的直觉是「React 性能不行」,这个直觉后来被证明是错的。

图片

后来我把同一个页面分别用 Vue 3.4 和 Svelte 5 又写了一遍(对,我有强迫症,主要是想搞清楚自己到底在骂什么),三个版本最后都能跑到 60fps。差别不在性能上,在于我为了达到 60fps 付出了什么。

先说个可能得罪人的结论:框架选型讨论里最没用的东西就是 benchmark。js-framework-benchmark 那个经典的「创建 1000 行 / 更新 1000 行 / 交换行」测试,React 和 Svelte 的分数能差好几倍,但那是空组件、纯数字、没有任何业务逻辑的场景。真实项目里你很难让 1000 行同时更新,瓶颈大概率在别的地方——首屏 JS 体积、hydration 时间、或者某个人写了个 O(n²) 的 filter 塞在 computed 里。拿 benchmark 选框架,跟看零百加速买家用车差不多。

真正该问的问题是:重渲染的边界归谁管

图片

React 的答案是:交给你。useState 触发更新,React 从那个组件开始往下重新执行整棵子树,除非你用 memo / useMemo / useCallback 手动砍断。这套机制非常可预测——你写什么就是什么,没有魔法,断点打下去调用栈是完整的。代价是你得一直惦记着它。我那个表格最后是靠 react-window 做虚拟滚动、把 Cell 用 memo 包起来解决的,但 memo 的浅比较在 props 里有对象字面量的时候就失效了,你还得配 useCallback。这种手动挡在超过 10 个人的项目里很容易失控,尤其是有后端转过来的人,他大概率不知道 style={{width: 100}} 每次渲染都是新对象。

Vue 的答案是:交给运行时的依赖图。你在 setup 里访问了 state.keyword,Vue 通过 Proxy 记下「这个组件的渲染 effect 依赖了 keyword」,keyword 变了只通知这个 effect。Vue 3.4 重写了这套响应式系统,用一个双向链表做依赖收集,官方博客里提到在一些大型响应式数组的场景下内存占用降了 56%。

图片

Svelte 4 的答案是:交给编译器。编译器看一眼你的模板,发现 {#each} 只依赖 list,就生成只更新这部分 DOM 的代码,连虚拟 DOM 都不需要。

Svelte 5 反而往后退了一步,我觉得这是对的

有意思的是 Svelte 5 没有继续沿着「更多编译时优化」这条路走。它引入的 runes——$state、$derived、$effect——本质上把一部分响应式判断从编译时挪回了运行时。以前 .svelte 里写 let count = 0 就是响应式的,现在你必须写 let count = $state(0)。官方给的理由我记得大概是:隐式响应式在跨模块共享状态的时候会失效(你把 let count 从组件挪到 .js 文件,它就不响应了,这个坑我在 Svelte 4 里踩过),而且编译时静态分析没法覆盖动态场景。

图片

这个改动让 Svelte 5 的运行时比 Svelte 4 大了一些,也劝退了一部分老用户。但我觉得它承认了一件重要的事:纯编译方案有天花板。编译器再聪明,也没法在构建阶段知道运行时才会出现的数据依赖。

所以你其实是在选「在哪付账」

我现在不太说哪个框架更好,我更愿意说复杂度是守恒的,你不在 A 处付账就在 B 处付账。

图片

React 把复杂度放在开发者身上,换来调试时的确定性——状态就是那几个 useState,数据流是显式的。Vue 把复杂度放在运行时,换来写业务代码时的省心,代价是偶尔会遇到「我明明没改它为什么更新了」,以及 ref 在模板里自动解包、在 script 里要 .value 这种不一致。Svelte 把复杂度放在编译器和构建流程上,换来产物小、运行快,代价是生态工具链(尤其 IDE 支持和调试体验)比前两个弱一档,编译器的行为有时候不够透明,出问题不好查。

还有个容易被忽略的维度:首屏预算。如果你的项目面向 C 端,要在 3G 网络下把首屏控制在 2 秒内,那 ReactDOM 那 40 多 KB gzip 就是实打实的成本——react 加 react-dom 18 生产构建大概 45KB gzip,Vue 3 runtime-only 大概 23KB,Svelte 编译产物小项目通常能压到 10KB 以内,Solid 的运行时我记得官方说是 7KB 左右。这还没算 hydration 的时间。如果能接受 Astro 或者 Qwik 这类岛屿 / 可恢复方案,那是另一条路,React Vue Svelte 都能当它们的岛屿组件用。

图片

我自己的几条判断标准

  1. 团队里有 3 个以上后端转前端的人,或者前端平均工龄不到 2 年——选 Vue。不是因为它简单,是因为它的默认路径容错率高,写错了不太会炸。
  2. 项目要长期维护、组件库要自己造、或者有复杂的交互状态机——选 React。生态里能直接抄的东西最多,遇到问题搜得到答案。但要提前定好 memo 和 useCallback 的使用规范写进 lint 规则,不然两年后你会想重写。
  3. 内容型站点、营销页、文档站——别用前端框架硬扛,直接上 Astro,把交互部分做成岛屿。
  4. 对包体积极度敏感(嵌入式、低端安卓机)——Solid 或者 Svelte 值得试。Solid 的 createSignal 返回 getter/setter,组件函数只执行一次,没有虚拟 DOM。
  5. 如果你就是想知道 Vue 的响应式和 React 到底差在哪,花一个周末拿 @vue/reactivity 这个独立包写个小东西,比你刷十篇对比文章有用。

最后说个可能不太受欢迎的观点:大部分团队纠结框架选型花掉的时间,够他们把现有项目的性能问题修完两遍了。我见过太多项目,选型会上吵了三个星期,最后上线卡顿的原因是没人给图片加 loading="lazy",也没人做路由懒加载。

🏷️ 标签: