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 必须 必须
相关推荐
天衍四九-1 小时前
Docker Compose企业实战系列(三):Vue/React前端项目容器化完整部署
前端·vue.js·docker
Rosanci1 小时前
从零到合入主线:我的 DeepSeek Harness 开源贡献实战手记
开发语言·前端·开源
用户960819842231 小时前
多周期联看时十字光标对不齐:前端排查「时间桶」的三步清单
前端
墨鱼老师1 小时前
SpringBoot+Vue 毕设项目开发踩坑记录(前后端分离)
vue.js·spring boot·毕业设计·课程设计·前后端分离·java 毕设
Data analyse4562 小时前
数据合规的敏感数据怎么识别?
前端·人工智能·数据分析
雪芽蓝域zzs2 小时前
Vue3 + Element Plus el-select 下拉弹窗
vue.js·elementui
陪我一起学编程2 小时前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
Wx-bishekaifayuan2 小时前
springboot房屋租赁系统11574-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·sql·spring·课程设计
小白的码BUG之路2 小时前
Docker -- 构建nginx镜像,启动vue项目
vue.js·nginx·docker