AI全栈实战 | 2.2-02 React 思维模型:数据不可变 vs Vue 数据可变,两种哲学把复杂度推给谁

上篇回顾: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 算法的三个假设

  1. 不同类型节点 → 直接替换(div 变 span,整个子树重建)
  2. 同类型节点 → 复用 DOM,更新属性
  3. 列表 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 做两件事:

  1. 更新内部状态(创建新值,不修改旧的)
  2. 触发组件重新渲染

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)。每次修改都创建新值,旧值不变。

好处:

  1. 时间旅行(undo/redo):旧状态还在
  2. 性能优化:引用比较比深比较快
  3. 并发安全:不可变数据天然线程安全

代价:

  1. 内存开销:每次创建新对象
  2. 心智负担:要记住不能直接改

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 必须 必须
相关推荐
林恒smileZAZ15 分钟前
浏览器为什么加载不了超大图片
前端·vue.js·html5
hhwyqwqhhwy1 小时前
设备树之 provider 和 consumer
java·linux·前端
垆边人似月.2 小时前
Python中如何使用get函数实现频次查询
服务器·前端·python
moshuying2 小时前
DeepSeek开源生产级推理框架DeepInfer:单集群一天处理155万亿token,这意味着什么?
前端
子兮曰3 小时前
PHP漏洞危机,你的网站升级了吗?
前端·后端·php
jerrywus3 小时前
给 Pi 装上生图插件:从登录报错到画出一只 200×200 的猫
前端·agent·claude
陈随易3 小时前
搞钱点子之生态鱼缸
前端·后端·程序员
PBitW3 小时前
从空目录到上线:我全程用 TRAE 做了一款微信小游戏合集(含一次完整架构重构)
前端·trae
不爱学英文的码字机器4 小时前
从Q1迁到T1,同一个编号已经换了含义
前端·数据库·microsoft
云和数据.ChenGuang4 小时前
光哥说Python 循环导入(Circular Import)深度解析:成因、排查与解决方案
开发语言·前端·人工智能·python·langchain