写在前面
本文不深入探讨 React 与 Vue 的源码或具体架构实现,而是站在一位同时使用过 Vue 和 React 的应用开发者视角,去感受和理解主流前端框架在设计上的各种思想与创意。凡是能被接纳并普及的东西,都有其存在的道理,本文只讲述主观感受,不涉及站队或评判某个框架的优劣~~
前端开发框架初体验
其实,目前几乎所有类似 React 或 Vue 的前端框架,核心都在解决前端开发中的两大问题:
1. 组件复用
一个应用的多个页面通常需要统一风格,因此按钮、开关等通用组件往往需要被多次复用。框架通过组件化机制,让这些 UI 单元可以被方便地拆分、封装和复用。
2. 视图与数据的关系
在大多数应用中,用户既可以通过视图操作影响数据,也可以通过改变数据来更新视图。写过传统原生三件套(HTML + CSS + JS)的开发者对此深有体会:每一次交互都需要手动修改变量或操作 DOM。当项目逐渐复杂庞大时,这些散落各处的 DOM 操作和维护逻辑会迅速失控,变得难以维护。
框架正是通过响应式数据绑定、声明式视图等机制,把"数据驱动视图"变成默认范式,从而显著降低这种维护成本。
当然目前很多框架能解决的问题不止这两个,还包括了很多其他特性,例如虚拟 DOM 映射、事件统一管理等等

React
React 是 facebook 团队出品的一款前端开发框架,但它其实更像一个库?它本身只负责 UI 渲染,路由、状态管理等由你自由选型,所以官方更倾向于叫它"用于构建用户界面的库",但实际使用上通常都会和其他很多库绑定使用,所以这个库和框架的界限比较模糊
React 的视图构建特性
React 将 UI 构造成组件树。每个组件可以维护自己的内部状态(state),并通过 props 接收父组件传来的数据(这些数据可能来自父组件的 state)。组件对外暴露 props 接口、对内封装状态与逻辑,因此成为可组合、可复用的独立部件。数据自上而下流动,状态应放在真正需要它的最近公共祖先中

React 的视图搭建采用 JSX/TSX------一种可嵌入 JavaScript 表达式的类 HTML 语法扩展,经编译转为 React 元素。它没有专用模板指令,列表渲染用 map()、条件渲染用 &&/三元表达式,全部是原生 JS 表达式,因此组合视图时灵活简便。但 {} 内只能放表达式而非语句,且 JSX 是声明式描述"给定数据下视图应呈现什么",实际 DOM 更新由 React 渲染机制完成,开发者并不直接命令式地操控视图
jsx
const products = [
{ title: 'Cabbage', isFruit: false, id: 1 },
{ title: 'Garlic', isFruit: false, id: 2 },
{ title: 'Apple', isFruit: true, id: 3 },
];
export default function ShoppingList() {
const listItems = products.map(product =>
<li
key={product.id}
style={{
color: product.isFruit ? 'magenta' : 'darkgreen'
}}
>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
}
React 的状态设计
React 的状态更新是声明式的:调用 setState 只是向 React 发起一次更新请求,不会立即改变当前代码中的变量值,也不会由开发者直接控制渲染时机。React 会将请求排队、合并(自动批处理),在调度后重新渲染;若新旧值相等还可能跳过更新。开发者能明确看到并决定的是更新逻辑与状态归属 ,而执行时机由 React 掌控。当前代码中读到的始终是本次渲染的快照值
jsx
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (<button onClick={handleClick}>Clicked {count} times</button>);
}
React 处理视图与数据关系的策略是手动通知式 :调用
setXXX向 React 发起渲染请求,不调用则框架不会响应。Flutter 的setState采用了同样的契约
并且 React 还有一个重要的不可变特性,这个特性核心规则是:永远不要直接修改 state 或 props,而是创建一个新的副本,把新副本交给 React 。这不是 JavaScript 语言层面的强制约束,而是 React 运行时依赖的契约。直接修改(mutation)会导致一个致命问题:React 感知不到变化
如果你对数组调用
push,或直接给对象属性赋值,引用地址没有变。React 用Object.is比较新旧 state,发现引用相同,就会跳过重新渲染。界面不会更新,同时你还在无意中篡改了上一次渲染的"快照",导致调试时历史日志被污染。更深层的原因在于 React 的设计选择:它不劫持对象属性、不依赖 Proxy 追踪变化,因此你可以在 state 里放任意大的对象而无需额外性能开销
实际使用中通常结合 Immer 库一起使用,Immer 库赋予了在 React 中用"直接修改"的写法,自动得到不可变新状态 的能力(主要针对嵌套层数较深的对象,对这种对象做拷贝是比较麻烦的),它本质是 Proxy ,会记录你对它做的所有"修改"操作。回调结束后,Immer 根据这些记录,生成一个全新的不可变对象,原对象完全不受影响。所以代码看起来像在"违反规则"直接改,但底层产出的是符合 React 要求的新引用
js
import { useImmer } from 'use-immer';
const [person, updatePerson] = useImmer({ name: 'A', artwork: { city: 'Paris' } });
updatePerson(draft => {
draft.artwork.city = 'Lagos'; // 看起来在直接改,实际不是
});
感受
用 React 开发时,一个突出感受是它很贴合普通 JS/TS 的写法:组件都是函数,直接返回视图;状态更新和记忆类 Hook 也以函数调用的形式使用。整体手感接近原生 JS/TS 开发,侵入性较低。对从 Node.js 后端转来的开发者来说,通常会感到比较亲切。
不过,亲切的主要是语法形态和组合方式。真正的差异在运行时契约:组件函数会被反复执行,局部变量不会跨渲染保留,因此需要 Hook 来保存状态;组件体必须保持纯净,副作用要隔离到 useEffect 或事件处理函数中;Hooks 还有调用顺序的约束。
换句话说,React 让 UI 逻辑"长得像普通函数",但要求你按"UI 是状态的纯函数、渲染可重复执行"来思考,跨过这一层认知后,后端经验反而会成为加分项
Vue
按官网文档的说法:Vue(发音 /vjuː/,类似 view)是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,提供了一套声明式、组件化的编程模型,帮助开发者高效地开发用户界面。Vue 同样是一款声明式前端框架,但其整体设计思想建立在响应式之上
Vue 的视图构建特性
Vue 同样将 UI 构造成组件树。每个组件可以维护自己的内部状态(ref、reactive),并通过 props 接收父组件传来的数据(这些数据可能来自父组件的状态)。组件对外暴露 props 接口、对内封装状态与逻辑,因此成为可组合、可复用的独立部件。数据同样自上而下流动,状态应放在真正需要它的最近公共祖先中
Vue 对组件的抽象与视图构建,可以概括为:以单文件组件(SFC)为封装单元,以模板声明视图结构,最终编译为渲染函数,产出虚拟 DOM 并由响应式系统驱动更新。它与 React 最大的不同在于,Vue 同时掌控编译器和运行时,在编译阶段做更多的静态优化,而非纯运行时的全量比对
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: bold; }
</style>
Vue 的响应式
"响应式"这个词在 Vue 语境下,核心意思是:数据变了,依赖这份数据的地方会自动跟着更新,不需要手动去通知
类似 Excel 表格中你在 A1 填
10,B1 写公式=A1*2,B1 显示20。当你把 A1 改成20,B1 自动变成40,你不需要去操作 B1
Vue 3 用 Proxy 代理你的状态对象。当你写 state.count 读取属性时,Proxy 的 get 拦截器被触发,Vue 此时记录:"当前正在执行的副作用(比如组件的 render 函数、一个 computed、一个 watchEffect)读取了 count。" 当你写 state.count = 1 赋值时,Proxy 的 set 拦截器触发,Vue 查出"哪些副作用依赖了 count",然后把它们重新执行一遍
所以响应式的两个支柱是:
- 依赖收集:读数据时,记下"谁在读"
- 触发更新:写数据时,通知"读过它的人"重新执行
感受
用 Vue 开发时,一个突出感受是它很贴近传统前端开发的直觉:视图写在 <template> 里,结构和 HTML 一脉相承;逻辑写在 <script> 里,状态用 ref / reactive 声明,改数据即可,界面自动跟着变。整体手感接近"增强版的 HTML + JS",有点像在编写"配置",对从 jQuery 或原生前端转来的开发者来说,通常会感到比较亲切
不过,亲切的主要是写法形态和语法外观。真正的差异在运行时机制:模板不是浏览器直接解析的 HTML,而是先被编译成 render 函数,再产出虚拟 DOM 并挂载;状态不是普通变量,而是被 Proxy 包裹的可观察对象,读写会触发依赖收集与更新通知;computed、watch、生命周期钩子也都有各自的执行时机和依赖追踪规则。
换句话说,Vue 让视图"长得像 HTML",但要求你按"数据是响应式源、模板是它的函数映射、更新由依赖自动驱动"来思考。跨过这一层认知后,传统前端的结构直觉反而会成为加分项