Vue 和 React 有什么区别?该怎样选?
Vue 用响应式系统追踪组件读了哪些数据,数据一改只更新用到它的组件,模板经过编译器优化;React 在状态变化时重新执行组件函数,默认连同子组件一起重新渲染,靠 memo 或 React Compiler 跳过不必要的部分。两者都能做大型应用,选择主要看团队经验、生态依赖和渲染部署方式。
简答
同样是点击按钮让计数加 1,Vue 里改的是
count.value,Vue 记得哪些组件在渲染时读过它,只让这些组件更新;React 里调用的是setCount,React 会重新执行这个组件函数,默认还会把它下面的子组件也重新渲染一遍,再比较出要改的 DOM。这个差别来自两者追踪变化的方式。Vue 用 Proxy 和 getter/setter 在读写时记录依赖,属于“知道谁用了什么”;React 不追踪读取,状态变了就从这个组件往下重新计算,属于“重新算一遍再比较”。所以 React 里常要用
memo、useMemo跳过子组件,或者交给 React Compiler;Vue 里这类手动优化少一些。写法上,Vue 推荐单文件组件和模板,编译器能分析模板里哪些部分是静态的;React 用 JSX,界面就是 JavaScript 表达式,灵活但编译器能做的静态分析少。生态上,Vue 有官方的 Vue Router、Pinia;React 官方只管核心库,路由、状态管理由社区方案承担,新项目文档推荐从 Next.js、React Router 这类框架起步。
选择时不要比较语法好坏,两者都能做大型应用。先看团队熟悉哪个、关键依赖支持哪个、需要的渲染和部署方式有没有成熟方案,再用一条真实业务流程做原型验证。
一次更新各自做了什么
先看同一个计数器,注意两边“改状态”的写法:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
<HeavyList />
</template>
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<HeavyList />
</>
);
}
点击按钮后,HeavyList 会不会重新渲染?Vue 里不会,React 里默认会。
Vue 的每个组件实例在渲染时是一个响应式 effect,渲染函数读到 count 就把自己登记为它的依赖。count 改了,Vue 只重新执行登记过的那个渲染 effect。HeavyList 没有读 count,它的 props 也没变,所以不用更新。
React 不记录读取。setCount 让 Counter 重新渲染,React 文档说明这个过程是递归的:组件返回了别的组件,就接着渲染那个组件,直到没有更深的组件为止。所以 HeavyList 也会被调用一次,虽然最后 DOM 可能没有任何变化。想跳过它,可以用 memo 包起来,让它在 props 不变时不重新渲染;启用 React Compiler 后,编译器会在构建时自动加上这类缓存。
模板和 JSX 的差别在哪
Vue 的模板会被编译成渲染函数。因为编译器能看到整份模板,它会给动态节点打上标记(patch flag),说明这个节点只有 class 会变还是只有文本会变;还会把动态节点收集到一个扁平数组里,更新时只遍历它们,静态部分直接跳过。Vue 文档把这称为“编译器提示的虚拟 DOM”。
JSX 只是 React.createElement 一类调用的语法糖,任何 JavaScript 表达式都可以写进去,所以写法更自由,比如用 map 和三元表达式随意组合。代价是 React 运行时拿不到“哪些部分永远不变”的信息,要靠比较新旧虚拟 DOM 找出变化。Vue 也支持写渲染函数和 JSX,只是官方说大多数场景模板就够用。
状态和数据流有什么不同
两者都是单向数据流:父组件通过 props 往下传,子组件通过事件或回调通知父组件。差别在于状态本身。
Vue 的状态是可变的响应式对象,直接 state.list.push(item) 就能触发更新。React 的状态要当作不可变的值,更新数组得写 setList([...list, item]),因为 React 用 Object.is 比较新旧值决定是否更新,原地修改后引用没变,就可能不会重新渲染。
这也影响了状态库的风格。Vue 官方推荐的 Pinia 直接修改 store 里的状态;React 生态里的 Redux 要求 reducer 不修改原状态、返回新对象,Redux Toolkit 借助 Immer 才允许写成“直接修改”的样子。
怎样选
可以按下面几件事排除:
- 团队经验:已有成员熟悉哪一个,迁移成本有多大。
- 关键依赖:要用的组件库、图表、编辑器、跨端方案支持哪一个。比如要用 React Native 做原生应用,选 React 更顺。
- 渲染与部署:需要 SSR 或静态生成时,React 这边常见的是 Next.js、React Router 框架模式,Vue 这边是 Nuxt,看哪一个满足部署环境。
- 原型验证:挑一条真实的复杂流程,两边各写一版,比较开发效率和维护成本。
更完整的约束清单见《选择前端框架时应该先比较哪些约束?》。
常见追问
Vue 是不是一定比 React 快?
不能这样说。Vue 默认能少做一些不必要的组件渲染,React 可以用
memo或 React Compiler 达到类似效果。真实应用的性能更多取决于数据量、列表怎样渲染、请求怎样组织,要用性能工具实际测量,不要拿基准测试排名做决定。
React Hooks 和 Vue 组合式 API 看起来很像,区别在哪?
组合式 API 的
setup只在组件创建时执行一次,里面的ref、computed一直存在,所以没有调用顺序的限制,也不会读到旧闭包。React 组件函数每次渲染都重新执行,Hooks 靠调用顺序对应状态,所以不能写在条件分支里,Effect 还要正确声明依赖。
Vue 里需要 memo 这类优化吗?
需要的场景少,但不是没有。Vue 提供
v-once、v-memo、shallowRef、markRaw处理大列表、大对象这类情况,详见《v-once、v-memo、shallowRef 和 markRaw 分别在什么时候用?》。