最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
从 Vue3 到 React · 快速上手系列第 6 篇:状态管理 useState 与 useReducer实战解析
时间:2026-07-30 14:14:49 编辑:袖梨 来源:一聚教程网
本文围绕从 Vue3 到 React · 快速上手系列第 6 篇:状态管理 useState 与 useReducer实战解析展开,先梳理核心概念,再结合实践场景说明步骤、代码思路和容易忽略的细节,方便后续直接参考。
这是整个系列最核心的一篇。Vue 的响应式(ref / reactive)是它的灵魂,React 对应的就是 useState。但两者背后的机制截然不同——理解这个差异,你就真正"懂" React 了。

一、useState vs ref:第一眼
复制代码// Vue3 const count = ref(0) count.value++ // 直接改,视图自动更新 复制代码// React const [count, setCount] = useState(0) setCount(count + 1) // 调用 setter,请求重新渲染 useState(初始值) 返回一个数组:[当前值, 更新函数]。这里用了 JS 的数组解构,你可以随意命名,约定是 [x, setX]。
核心差异:
- Vue:
count是个响应式容器,你改.value,Vue 精准更新用到它的地方。 - React:
count就是本次渲染的一个普通数值快照;调用setCount不是"改变量",而是告诉 React"用新值重新渲染整个组件"。
二、为什么必须用 setter,不能直接改?
回顾第 1 篇的核心:React 组件每次渲染都重新执行整个函数。普通变量每次都会被重置,所以 React 需要把状态存在自己内部(Fiber 里),每次渲染时通过 useState 把最新值"发"给你。
复制代码function Counter() { const [count, setCount] = useState(0) // 直接改普通思维会失效 const bad = () => { count = count + 1 } // 视图不会更新 // 必须通过 setter 请求渲染 const good = () => setCount(count + 1) return <button onClick={good}>{count}button> } 三、state 是"快照",不是"实时值"
Vue 里改完 count.value 立刻就能读到新值。React 里,在同一次渲染中,state 永远是那次渲染时的值,不会中途变化:
复制代码function Demo() { const [count, setCount] = useState(0) const handleClick = () => { setCount(count + 1) console.log(count) // 打印的还是旧值!新值下次渲染才生效 } // ... } 更经典的坑——连续调用多次:
复制代码const handleClick = () => { setCount(count + 1) // count 是 0,请求渲染为 1 setCount(count + 1) // count 还是 0,又请求渲染为 1 setCount(count + 1) // 结果只 +1,而不是 +3! } 解决办法:用"函数式更新",参数是"上一个最新值":
复制代码const handleClick = () => { setCount(prev => prev + 1) // 0 -> 1 setCount(prev => prev + 1) // 1 -> 2 setCount(prev => prev + 1) // 2 -> 3 } 四、对象与数组:不可变更新(Vue 开发者最不适应的点)
Vue 的 reactive 对象可以随便原地改:
复制代码// Vue3 const user = reactive({ name: 'Tom', age: 20 }) user.age = 21 // 直接改,OK const list = reactive([1, 2]) list.push(3) // 直接 push,OK React 的 state 必须创建新对象/新数组替换,不能原地修改:
复制代码const [user, setUser] = useState({ name: 'Tom', age: 20 })// 原地改,React 察觉不到(引用没变) user.age = 21; setUser(user)// 用展开运算符创建新对象 setUser({ ...user, age: 21 }) 数组同理,用返回新数组的方法(map/filter/concat/展开),别用 push/splice 这类原地修改:
复制代码const [list, setList] = useState([1, 2, 3])setList([...list, 4]) // 新增 setList(list.filter(n => n !== 2)) // 删除 setList(list.map(n => n === 2 ? 20 : n)) // 修改 为什么? React 靠"引用是否变化"来判断要不要重渲染。原地修改引用没变,React 以为没变化。这条"不可变原则"是 React 的铁律。
五、多个状态:拆开还是合并?
Vue 常把相关数据放一个 reactive 对象。React 里两种都行,原则是:
复制代码// 独立、无关的状态 → 拆开(推荐) const [name, setName] = useState('') const [age, setAge] = useState(0) const [loading, setLoading] = useState(false)// 强相关、总是一起更新的 → 合并成一个对象 const [form, setForm] = useState({ name: '', email: '', phone: '' }) setForm(prev => ({ ...prev, name: 'Alice' })) 六、惰性初始化:昂贵的初始值只算一次
如果初始值需要昂贵计算(比如读 localStorage),传一个函数给 useState,它只在首次渲染执行一次:
复制代码// 每次渲染都执行 expensiveInit() const [data, setData] = useState(expensiveInit())// 只在首次渲染执行一次 const [data, setData] = useState(() => { const saved = localStorage.getItem('data') return saved ? JSON.parse(saved) : defaultData }) 这个坑正是因为"组件每次渲染都重跑整个函数"导致的——牢记第 1 篇的核心。
七、useReducer:复杂状态逻辑的利器
当一个状态有多种更新方式、逻辑复杂时(比如一个表单、一个购物车),useState 会散落一堆 setter。这时用 useReducer,把所有更新逻辑集中到一个 reducer 函数里。它类似于把 Pinia 的 actions 收拢在一起的思路:
复制代码import { useReducer } from 'react'// reducer:根据 action 返回新 state(注意仍然是不可变更新) function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } case 'set': return { count: action.payload } default: return state } }function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }) return ( <div> <span>{state.count}span> <button onClick={() => dispatch({ type: 'increment' })}>+button> <button onClick={() => dispatch({ type: 'decrement' })}>-button> <button onClick={() => dispatch({ type: 'set', payload: 0 })}>清零button> div> ) } 选择建议:
- 简单、独立的值 →
useState - 多字段、多操作、更新逻辑复杂 →
useReducer - 跨组件的全局状态 → Context 或 Zustand/Redux(第 11 篇讲)
八、对照速查
| 需求 | Vue3 | React |
|---|---|---|
| 基本类型状态 | ref(0) | useState(0) |
| 读取值 | count.value | count(直接用) |
| 修改值 | count.value++ | setCount(c => c + 1) |
| 对象状态 | reactive({}) | useState({}) + 展开更新 |
| 修改对象属性 | obj.a = 1 | setObj({...obj, a: 1}) |
| 数组增删 | arr.push() | setArr([...arr, x]) |
| 复杂状态逻辑 | reactive + 方法 | useReducer |
小结
useState返回[值, setter],改状态只能调 setter(请求渲染),不能直接赋值。- state 是快照:同一次渲染内不会变;新值依赖旧值时用
setX(prev => ...)。 - 不可变更新是铁律:对象/数组要创建新的替换,嫌麻烦可用 Immer。
- 惰性初始化
useState(() => ...)处理昂贵初始值。 - 复杂状态逻辑用
useReducer集中管理。
下一篇讲副作用与生命周期 —— watch/watchEffect/onMounted 如何翻译为 useEffect——它一个 Hook 就统一了你熟悉的 watch、watchEffect、onMounted、onUnmounted。
实际使用时,建议结合项目规模、依赖环境和团队习惯做取舍;先保证流程清晰和结果可验证,再逐步优化细节。
相关文章
- 驯龙之旅莱克角色强度如何攻略 07-30
- 赛尔号萨斯洛克如何样 07-30
- 谍惊蛰舞厅惨案如何触发攻略 07-30
- 漫画群星大集结灶门祢豆子好玩吗 漫画群星大集结灶门祢豆子强度分析攻略 07-30
- 少林宗师之上如何搭配 07-30
- 冒险岛台服怀旧服预创角色开放时间揭晓|冒险岛台服怀旧服预创建角时间表攻略 07-30