Skip to content
Vue 与 React 概述:框架定位、核心特点与适用场景
前端框架的使命
原生 JavaScript 操作 DOM 构建 UI 的基本流程:document.createElement 创建元素,逐个设置属性、拼接嵌套结构,数据变化后手动找出需要更新的节点并修改。[2] 如果界面足够简单,这件事本身并不复杂。但页面交互一旦增多,手动维护 DOM 状态与 JavaScript 数据状态的一致性会迅速变成体力活——哪个节点对应哪条数据、数据变了该改哪里、DOM 改完数据要不要同步回来,这些问题缺少统一的处理方式,只能依靠开发者的个人约定来维持。
框架的出现,本质上是对这种「手动同步」模式的抽象。多数框架做了三件事 [1][3]:
- 将界面拆成组件——可维护、可复用、自带结构的代码单元,而非散落各处的 DOM 操作片段。
- 提供声明式的描述方式——描述「界面应该长什么样」,框架负责计算并执行实际的 DOM 操作。
- 建立可预测的状态更新路径——数据变化时,界面按规则更新,而不依赖开发者逐个手动处理。
组件化、声明式、可预测性。三者使得代码在项目规模扩大后仍然可被理解和修改。
Vue:渐进式框架
Vue 的定位是「渐进式框架」[7]。
核心库与可选模块
Vue 的核心库只做一件事:声明式渲染和响应性 [6]。也就是说,给出 HTML 模板与 JavaScript 状态之间的对应关系,Vue 在状态变化时自动更新 DOM。
核心库体积不大,可单独引入现有 HTML 页面,不需要构建工具,一个 <script> 标签就能用。例如一个计数器:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">
<button @click="count++">Count is: {{ count }}</button>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const count = ref(0)
return { count }
}
}).mount('#app')
</script>点击按钮,count 递增,页面数字跟随变化。整个过程没有手动选择 DOM 元素,没有 innerHTML 或 textContent 赋值。Vue 追踪到 count 被修改,自动更新依赖它的 DOM 节点。
在此基础上,Vue 的生态提供了路由(Vue Router)、状态管理(Pinia)、服务端渲染(Nuxt)、静态站点生成等模块。关键点在于:这些模块都不是必选的。可以只用核心库做页面中的某个小组件,也可以在项目复杂度上升后逐步加入路由和状态管理。这就是「渐进式」的实际含义——不是 all-or-nothing 的框架选择,而是根据场景按需装配。
模板与单文件组件
Vue 的单文件组件(Single File Component,SFC)用 .vue 文件将模板、脚本、样式组织在一起 [8]:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button { font-weight: 600; }
</style>模板语法是标准 HTML 的扩展,事件绑定用 @click,数据绑定用 {{ }}。<style scoped> 将样式限制在当前组件范围内,不会泄漏到外部。对于从 HTML/CSS/JS 传统开发模式转过来的开发者,这种组织方式的学习曲线相对平缓——仍然在和 HTML、CSS、JavaScript 打交道,只是它们被 Vue 的编译器连接在了一起。
React:视图是状态的函数
React 将 UI 建模为一个函数:UI = f(state)。给定状态,函数返回对应的视图描述 [10]。状态变化时,React 重新执行该函数,计算出新的视图描述,对比前后版本的差异,将最小的变更应用到 DOM。
组件模型
用函数写同样功能的计数器:
jsx
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count is: {count}
</button>
)
}useState(0) 返回一个状态值和它的更新函数。setCount(count + 1) 触发状态更新时,React 重新调用 Counter 函数,用新的 count 值生成新的 JSX 输出,然后 diff 出 {count} 这个文本节点的变化并应用到 DOM。
这里的核心规则是不可变更新:setCount 传入新值,而非修改原来的 count 变量。count 本身是只读的,每次更新生成一个新的状态快照。带来的效果是状态变更路径清晰——任何时候看到的 count 值就是那一刻的状态,不会出现「变量已被别处修改但无法感知」的情况。
单向数据流与 JSX
React 组件之间通过 props 传递数据,方向严格从父到子。子组件如需通知父组件,通过回调函数完成,而非直接修改父组件的状态。
JSX 是这个模型下的产物——将 HTML 标签和 JavaScript 表达式写在同一个语法结构里。JSX 本质上是 React.createElement() 调用的语法糖。每种标记最终被编译为普通 JavaScript 对象(Virtual DOM 节点),在运行时被 React 用来做 diff 和渲染。
框架特性速览
响应式 vs 单向数据流
Vue 的响应式系统自动追踪数据依赖。模板中使用了 count,Vue 就知道该模板片段依赖 count;count 变化时,只有对应的 DOM 部分被更新。对开发者来说,这更像「声明数据和模板的绑定关系」,之后的数据变化交给框架处理。
React 不自动追踪——状态更新触发整个组件函数重新执行,再通过 Virtual DOM diff 找出实际需要更新的 DOM。数据流方向始终单向:状态在某处定义,通过 props 往下传,事件回调往上冒。路径明确,不会出现一个数据变化导致不可预知的副作用。代价是开发者需要手动管理更新触发和性能优化(如使用 memo、useMemo 控制重渲染范围)。
两者的权衡点不同:Vue 的响应式让数据绑定更直接,但依赖追踪在调试时有时需要借助开发工具来理清依赖链路的实际走向;React 的单向数据流让状态变化始终可回溯,但要求开发者对渲染行为有更精确的控制意识。
模板/SFC vs JSX
Vue 的模板语法与 HTML 高度兼容。一段现成的 HTML 结构放进 .vue 文件的 <template> 中,加上必要指令基本就能工作。这对设计师交付的静态页面原型、或从旧项目迁移过来的场景较为友好。
JSX 把标记和逻辑放在同一个词法作用域内。任何 JavaScript 表达式——数组的 map、三元表达式、函数调用——都能直接出现在标记中,不需要单独的指令语法。当组件的渲染逻辑变得复杂(条件分支多、循环嵌套深),JSX 提供的灵活度更高;在简单场景下,模板的简洁性则更占优。
如何选择:场景与权衡
选择框架前,先看团队和项目适合哪种模式。
Vue 的渐进式特性在以下场景中价值明显:项目从小规模起步,未来可能需要扩展但不确定会走多远;团队有传统 HTML/CSS/JS 背景,不习惯 JS 和标记混写;希望在存量项目中逐步引入框架,而非推倒重来。
React 的 UI=f(state) 模型在状态复杂的应用中优势突出:交互密集、状态来源多样(服务端数据、本地缓存、用户操作、URL 参数)、需要严格追踪状态变化路径的场景。JSX 的编程灵活性在复杂渲染逻辑下也更游刃有余。React 生态覆盖面广,从 Web 到移动端(React Native)到服务端渲染都有成熟方案。
这不代表某个场景只能用某个框架。大多数中大型应用用 Vue 或 React 都能实现,差别更多体现在开发过程中的心智模型和团队习惯上。
后续对比分析框架
本篇梳理了 Vue 和 React 各自「是什么」以及「解决什么问题」。后续篇章沿一条逐渐深入的线索展开:定位 → 理念 → 机制 → 对比。
- 核心概念:Vue 的响应式数据与 React 的不可变状态各自的实现思路和代价。
- 组件模型:组合式 API 与 Hooks 在逻辑组织上的异同。
- 数据流:props、events 和全局状态管理方案的设计差异。
- 编译与运行时:模板编译与 JSX 转换的实际产出,以及 Virtual DOM 在各框架中的角色。
- 工程化:构建工具链、类型支持、路由和状态管理生态的成熟度对比。
每篇聚焦一个维度,用一致的框架去拆解两个框架在同一问题上的不同答案。
