为什么要谈框架选型
「Vue 和 React 该用哪个」是前端圈被问得最多的问题之一,但它的标准答案并不是某个名字,而是一组判断条件:团队既有技能栈、项目类型与生命周期、对服务端渲染的需求、招人与协作成本、以及生态成熟度。脱离这些条件谈优劣,讨论就会变成喜好之争。
好消息是,两者的差距比想象中小。它们都是声明式的组件化框架,都把「UI 是状态的函数」作为核心思想,都支持单文件/单组件组织、路由、状态管理、服务端渲染与静态生成。真正要比较的,是达成同样目标的路径差异,以及这些差异在你的团队里会放大成什么。
选框架不是在选「哪个更强」,而是在选「哪套约定更契合你们的团队与项目」。同一个团队用熟悉的技术做出的产品,通常好过用「更先进」的技术做出的半成品。
- 声明式 UI两者都以「状态决定视图」为内核,差别在响应式追踪与重渲染策略。
- 模板与 JSXVue 用模板编译,React 用 JS 表达结构,写法不同、心智模型不同。
- 状态管理Vue 侧 Pinia 为官方推荐;React 侧方案多样,需要团队自行收敛。
- 生态取舍Vue 倾向官方整合,React 倾向社区自由组合,选型即选治理方式。
核心理念:声明式 UI 与响应式数据
两者都主张「不要手动改 DOM,只描述状态与视图的对应关系」。分歧在于数据变化后如何知道该更新哪里。Vue 用响应式代理自动追踪依赖:数据被读取时记录依赖、被修改时通知对应的渲染函数,视图更新是「自动发生」的。React 把更新交给不可变状态与显式调用:你不能直接改 state,只能提交一个新值,框架重新执行组件函数并对比前后结果。
这个差别直接影响了日常写码的手感。Vue 里常见的是「改数据,视图自己变」;React 里常见的是「构造新对象,交给 set 函数」。两种方式都能写出高质量代码,但前者更贴近直觉,后者更强调数据不可变带来的可预测性。
| 维度 | Vue | React |
|---|---|---|
| 响应式来源 | 代理对象自动追踪依赖 | 不可变状态 + 显式更新函数 |
| 更新触发 | 数据变化即触发对应视图更新 | 调用更新函数后重新执行组件 |
| 组件描述 | 单文件组件:模板 + 脚本 + 样式 | 函数组件 + JSX + 独立样式方案 |
| 心智模型 | 贴近模板与数据绑定 | 贴近纯函数与不可变数据 |
| 上手曲线 | 较低,HTML 基础即可起步 | 中等,需要先理解函数式与 hooks |
需要澄清一个常见误解:Vue 的响应式不是「魔法」,它建立在 Proxy 与依赖收集之上,有明确的边界(例如原始值的重新赋值、数组索引的替换都需要按框架约定处理);React 的重渲染也不是「每次都全量」,它通过虚拟 DOM 对比与记忆化手段裁剪更新范围。理解机制,才能理解为什么某些写法会带来性能差异。
模板语法与 JSX:写法差异
两者最直观的差别在「怎么描述界面」。Vue 使用贴近 HTML 的模板,指令以 v- 前缀出现,事件用 @、绑定用 :,样式与逻辑写在同一个单文件组件里。React 则用 JSX——一段看起来像 HTML 的 JavaScript 表达式,结构、逻辑与数据都在同一个函数里表达。
<script setup>
import { ref, computed } from "vue";
const count = ref(0);
const double = computed(() => count.value * 2);
</script>
<template>
<button class="btn" @click="count++">
点击 {{ count }} 次 / 双倍 {{ double }}
</button>
</template>
// React:函数组件 + JSX
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
const double = count * 2;
return (
<button className="btn" onClick={() => setCount(count + 1)}>
点击 {count} 次 / 双倍 {double}
</button>
);
}
两段代码做的是同一件事,但阅读路径不同:理解 Vue 版本需要知道「模板里的表达式会被编译成渲染函数」,理解 React 版本需要知道「JSX 会被编译成函数调用」。这解释了各自的学习重点——学 Vue 更多是在学指令与配置约定,学 React 更多是在学 JavaScript 本身与 hooks 规则。
- 编译期能力。Vue 模板在编译期能做静态提升与补丁标记,运行时开销更可预测;React 的 JSX 更灵活,但把更多优化责任交给开发者。
- 样式组织。Vue 单文件组件支持
scoped样式,天然就近;React 需要 CSS Modules、CSS-in-JS 或原子化方案自行选型。 - 上手门槛。Vue 模板对后端或设计转前端的同学更友好;React 对习惯函数式写法的同学更顺手。
状态管理与数据流
组件之间的数据传递规则,两者高度相似:数据自上而下通过属性(props)传递,子组件通过事件或回调向上通知。props 向下、事件向上这条单向数据流是共同的基础约定,也是组件可预测的前提。
差别出现在「修改状态」这一步。Vue 允许直接修改响应式数据,框架负责追踪与更新;React 要求通过更新函数提交新值,以保证状态可比对、更新可批处理。
// Vue:直接改数据,视图自动更新
const todos = ref([]);
function add(text) {
todos.value.push({ text, done: false });
}
// React:构造新数组交给更新函数
const [todos, setTodos] = useState([]);
function add(text) {
setTodos(todos.concat({ text, done: false }));
}
跨组件共享状态时,Vue 有官方推荐的 Pinia,写法接近「可被任意组件读取的响应式仓库」;React 的选择更多,从轻量的自定义 hooks 到 Zustand、再到体系庞大的 Redux Toolkit,团队需要自己收敛出一套规范。
这里藏着一个重要的选型信号:Vue 提供的是「官方整合好的默认答案」,React 提供的是「可自由组合的零件」。前者减少决策成本,后者保留最大灵活度。团队越小、交付压力越大,默认答案的价值越高;团队规模大、有专门的基础设施组,自由组合的能力才划算。
生态与工程化:路由、构建与渲染
工程化能力决定了框架能不能支撑长期项目。两者在这方面的能力都足够完整,区别同样在「谁来提供」。
| 能力 | Vue | React |
|---|---|---|
| 脚手架 | create-vue(官方) | Vite 模板 / Next.js(社区) |
| 路由 | Vue Router(官方维护) | React Router(社区维护) |
| 状态管理 | Pinia(官方推荐) | Redux / Zustand 等社区方案 |
| 服务端渲染 | Nuxt(官方生态) | Next.js(社区生态) |
| 类型支持 | 组合式 API 与 TypeScript 配合良好 | TypeScript 生态成熟、类型定义完善 |
构建工具层面,两者都统一收敛到基于原生 ES 模块的现代打包方案,开发时按需编译、生产时打包优化,配置差异已经很小。真正影响项目的通常是服务端渲染与静态生成:Vue 侧对应 Nuxt,React 侧对应 Next.js,两者的路由约定、数据获取方式与部署模型都不同,若有 SEO 或首屏性能要求,应当把这一层纳入选型考量。
还有一个容易被低估的因素:移动端与跨端能力。希望一套代码同时产出 H5 与小程序、App 时,各生态下都有相应方案,但成熟度与踩坑成本不同。选型时应当把「未来半年有没有跨端的需求」提前问清楚。
性能与更新机制
很多人关心「哪个更快」,更准确的问法是「哪个更容易写出快的代码」。Vue 的响应式追踪能精确到组件级依赖,数据没被用到就不会触发更新;React 默认从更新所在的组件开始向后重渲染,需要借助记忆化手段裁剪范围。因此 React 项目里优化的动作更显式,也更容易被遗漏。
<!-- Vue:v-memo 跳过不必要的子树更新 -->
<li v-for="item in list" :key="item.id" v-memo="[item.done]">{{ item.text }}</li>
// React:memo 包裹组件,配合 useMemo 缓存派生值
const Row = memo(function Row({ item }) {
return <li className={item.done ? "done" : ""}>{item.text}</li>;
});
- 列表渲染都要稳定 key。用索引当 key 在增删排序时会引发错误复用,两者都会因此变慢。
- 大列表一律虚拟滚动。只渲染视口附近的行,这是数量级上的优化,与框架无关。
- 避免在渲染中做重计算。Vue 用计算属性、React 用 useMemo,把结果缓存起来。
- 别过早优化。先用性能面板定位瓶颈,再决定优化哪一处,凭空猜测通常改不到点上。
结论是:在多数业务系统里,两者性能差异远小于代码质量带来的差异。真正决定体感的是有没有做列表虚拟化、有没有过度渲染、有没有滥用全局状态,而不是框架名字。
团队与项目维度的选型清单
把前六节的分析收敛成可执行的判断。下面这张表不是绝对规则,而是把「常见情形」对应到「更省力的方向」,最终决策仍要结合你手上的具体条件。
| 项目情形 | 更倾向 | 主要原因 |
|---|---|---|
| 团队以前端为主、有多套自研基建 | React | 自由组合空间大,社区方案可选择面广 |
| 团队全栈为主、前端人力有限 | Vue | 官方默认答案多,决策成本低、上手快 |
| 需要快速交付中后台系统 | Vue | 生态内的组件库与模板成熟,开发节奏快 |
| 需要强 SEO、以内容为主 | 两者皆可 | Vue 用 Nuxt、React 用 Next.js,看团队熟悉度 |
| 跨端需求明确(H5 + 多端) | 按生态成熟度选 | 先确认目标平台在所选生态里的支持程度 |
| 招聘与人才供给是首要约束 | 按所在地区实际供给 | 招不到人的技术,再先进也落不了地 |
落地前建议按这份清单逐项确认:
- 技能盘点:团队现有成员对哪一套更熟,需要多少学习时间。
- 组件库:目标生态下有没有满足设计规范的组件库与图表方案。
- 构建与部署:在公司现有流水线上能否顺利接入,产物如何托管。
- 长期维护:未来两年谁来维护,文档与社区是否活跃。
- 退出成本:如果一年后要换方案,业务逻辑能否尽量与框架解耦。
迁移与共存的现实路径
现实中更常见的问题不是「从零选哪个」,而是「旧项目要不要换」。全量重写风险极高:需求在变、人力有限,重写周期内业务还要继续迭代。更稳妥的做法是渐进迁移——先让新老两套共存,再一块块替换。
// 渐进迁移:同一页面里让两套实现各管一块区域
// 1) 旧页面保持不动,新模块挂到独立容器
// 2) 两端通过自定义事件通信,避免直接依赖内部实现
container.dispatchEvent(new CustomEvent("cart:add", { detail: { id } }));
window.addEventListener("cart:add", function (e) {
console.log("收到跨实现消息:", e.detail.id);
});
迁移过程中有三条经验值得先记下来:
- 先统一边界,再替换实现。把状态与数据请求收口到独立层,UI 换框架时这部分不需要推倒重来。
- 按路由或页面切分,不要横切。一个页面内混用两套渲染会带来状态同步难题,按页面整块替换成本最低。
- 每完成一块就补测试。迁移最大的风险是行为悄悄改变,有回归测试才有底气继续推进。
选型是阶段性的最优解,不是终身承诺。今天合适的方案,两年后可能因为团队、业务或生态的变化而不再合适——把代码写得可迁移,比一次性选对更重要。
至此,前端开发的四个基础面——布局、DOM 与事件、框架写法和选型判断——就串成了一条完整的线。下一篇我们会回到工程视角,讨论构建、性能与可维护性这些让项目「跑得久」的话题。