上篇回顾:2.2-01 拆解了 Vue3 的 Proxy 响应式、组合式 API、组件通信、双向绑定本质。本篇讲 React------前端另一大框架。对后端工程师来说,React 的心智模型比 Vue 陡峭得多(JSX + Hooks + 不可变数据),但理解 React 能让你形成「为什么同样的需求两种框架设计完全不同」的判断力。

一、开篇:同一个需求,Vue 和 React 两种写法
需求:一个计数器,点击 +1,显示当前值。
Vue3:
vue
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
React:
jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
表面看差不多,但底层哲学截然不同:
- Vue:
count++直接修改数据,响应式自动更新 UI - React:
setCount(count + 1)创建新值触发重新渲染,不能直接修改 count
这就是本篇的核心命题:数据可变 vs 数据不可变,两种哲学各自把复杂度推给了谁。
二、JSX:HTML in JavaScript
2.1 JSX 是什么
JSX 不是 HTML,是 JavaScript 的语法扩展:
jsx
const element = <h1 className="title">Hello</h1>;
编译后变成:
javascript
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello'
);
所以 JSX 本质是 createElement 的语法糖。
2.2 与 Vue 模板的区别
| 维度 | Vue 模板 | JSX |
|---|---|---|
| 语言 | HTML 扩展(v-if/v-for) | JavaScript 表达式 |
| 灵活度 | 受限(模板语法) | 完全灵活(JS 能力) |
| 学习曲线 | 低(像 HTML) | 中(要懂 JS) |
| 类型检查 | 弱 | 强(TS 友好) |
Vue 模板把逻辑限制在模板语法内,JSX 把模板融入 JS。各有优劣------Vue 对新手友好,JSX 对高级用户灵活。
三、虚拟 DOM 与 diff 策略
3.1 为什么需要虚拟 DOM
直接操作真实 DOM 很慢(重排重绘)。虚拟 DOM 是一棵 JS 对象树,修改时先改虚拟 DOM,再 diff 出最小变化,批量更新真实 DOM。
javascript
// 虚拟 DOM 节点
{
type: 'div',
props: { className: 'box' },
children: [
{ type: 'span', props: {}, children: ['Hello'] }
]
}
3.2 diff 算法的三个假设
- 不同类型节点 → 直接替换(div 变 span,整个子树重建)
- 同类型节点 → 复用 DOM,更新属性
- 列表 diff → 用 key 标识(key 相同复用,不同销毁重建)
3.3 key 的重要性
jsx
// 没 key:React 按位置 diff,可能错乱
items.map(item => <li>{item.text}</li>);
// 有 key:React 按 key diff,精准匹配
items.map(item => <li key={item.id}>{item.text}</li>);
不要用 index 做 key------列表中间插入/删除时,index 变化导致 React 误判,性能差且可能 bug。
3.4 Vue 和 React 的虚拟 DOM 一样吗
底层思路一样(diff + 最小更新),但:
- Vue2:全量 diff
- Vue3:编译时标记静态节点,diff 时跳过(PatchFlag)
- React:Fiber 架构,可中断 diff(时间分片)
四、Hooks:函数式组件的状态
4.1 React 的函数式理念
React 推崇函数式编程:组件是纯函数,输入 props 输出 UI。但纯函数没有状态------Hooks 就是给函数组件加状态的「钩子」。
jsx
function Counter() {
const [count, setCount] = useState(0); // 状态钩子
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
4.2 useState 的正确理解
javascript
const [count, setCount] = useState(0);
count:当前值(不可变!不能count++)setCount:触发更新的函数0:初始值
setCount 做两件事:
- 更新内部状态(创建新值,不修改旧的)
- 触发组件重新渲染
4.3 useEffect:副作用钩子
jsx
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []); // 空依赖数组:只在挂载时执行一次
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
useEffect 的依赖数组:
- 不传:每次渲染后都执行
[]:只在挂载时执行[dep1, dep2]:dep 变化时执行
4.4 依赖数组的陷阱
jsx
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(setUser);
}, [userId]);
如果忘了加 userId 到依赖数组,userId 变了但 effect 不重新执行------数据不更新。这是 React 最常见的 bug 之一。
ESLint 的 react-hooks/exhaustive-deps 规则会自动检测这个问题。
五、数据不可变:React 的核心原则
5.1 为什么不能直接修改
jsx
// 错误:直接修改 state
const [list, setList] = useState([1, 2, 3]);
list.push(4);
setList(list); // React 不会重新渲染!
React 用 Object.is 比较新旧 state,引用相同就跳过渲染。list.push 没改引用,React 认为没变。
jsx
// 正确:创建新数组
setList([...list, 4]); // 新引用,触发渲染
5.2 不可变数据的哲学
React 的核心理念:数据不可变(immutable)。每次修改都创建新值,旧值不变。
好处:
- 时间旅行(undo/redo):旧状态还在
- 性能优化:引用比较比深比较快
- 并发安全:不可变数据天然线程安全
代价:
- 内存开销:每次创建新对象
- 心智负担:要记住不能直接改
5.3 Vue 的可变哲学
javascript
const state = reactive({ list: [1, 2, 3] });
state.list.push(4); // 直接修改,响应式触发
Vue 用 Proxy 拦截修改,知道你改了什么。不需要创建新对象。
5.4 两种哲学把复杂度推给谁
| 哲学 | 复杂度推给 | 优势 |
|---|---|---|
| React 不可变 | 开发者(要手动创建新值) | 性能优化简单(引用比较) |
| Vue 可变 | 框架(Proxy 拦截要追踪所有修改) | 开发者心智负担小 |
结论:React 把数据管理的复杂度推给开发者,换性能优化的简单;Vue 把复杂度吞进框架,换开发体验的简洁。没有对错,看团队偏好。
六、受控组件 vs 非受控组件
6.1 受控组件
jsx
function Form() {
const [text, setText] = useState('');
return <input value={text} onChange={e => setText(e.target.value)} />;
}
输入框的值由 React state 控制。每次输入都触发 setState → 重新渲染。
6.2 非受控组件
jsx
function Form() {
const inputRef = useRef();
return <input ref={inputRef} defaultValue="" />;
}
输入框的值由 DOM 自己管,React 通过 ref 读取。
推荐:表单用受控组件(数据在 React 里,便于校验和提交);简单场景用非受控(性能好)。
七、Vue vs React 选型对比
| 维度 | Vue3 | React |
|---|---|---|
| 心智模型 | 可变数据 + 响应式 | 不可变数据 + 重新渲染 |
| 模板 | 模板语法 | JSX |
| 状态 | ref/reactive | useState/useReducer |
| 副作用 | watch/watchEffect | useEffect |
| 生态 | 国内中后台强 | 国际化、跨平台 |
| TS 支持 | 好(Vue3 重写) | 极好(JSX 天然适配) |
| 学习曲线 | 平缓 | 陡峭 |
| 适合场景 | 管理后台、中后台 | 复杂交互、React Native |
后端转前端推荐 Vue3,但理解 React 能让你明白「为什么有不同的设计选择」。
八、小结表
| 概念 | React | Vue 对照 |
|---|---|---|
| 视图描述 | JSX | 模板 |
| 状态 | useState | ref/reactive |
| 数据原则 | 不可变 | 可变 |
| 副作用 | useEffect | watchEffect |
| 性能优化 | 引用比较 | Proxy 追踪 |
| 列表 key | 必须 | 必须 |