首页 > 网页制作 >Vue3转React快速上手系列6:状态管理useState与useReducer

Vue3转React快速上手系列6:状态管理useState与useReducer

来源:互联网 2026-07-31 17:05:08

先说结论:本篇是整个系列中最核心的内容。Vue 的响应式(ref / reactive)是其灵魂,而 React 中与之对应的核心是 useState。但两者背后的机制完全不同——理解这一差异,你才算真正“懂” React。 一、useState vs ref:第一印象 复制代码// Vue3 co

先说结论:本篇是整个系列中最核心的内容。Vue 的响应式(ref / reactive)是其灵魂,而 React 中与之对应的核心是 useState。但两者背后的机制完全不同——理解这一差异,你才算真正“懂” React。

Vue3转React快速上手系列6:状态管理useState与useReducer

长期稳定更新的攒劲资源: >>>点此立即查看<<<


一、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 篇介绍)

八、对照速查

需求Vue3React
基本类型状态ref(0)useState(0)
读取值count.valuecount(直接使用)
修改值count.value++setCount(c => c + 1)
对象状态reactive({})useState({}) + 展开更新
修改对象属性obj.a = 1setObj({...obj, a: 1})
数组增删arr.push()setArr([...arr, x])
复杂状态逻辑reactive + 方法useReducer

小结

  • useState 返回 [值, setter],修改状态时只能调用 setter(请求渲染),不能直接赋值。
  • state 是快照:同一次渲染内不会发生变化;新值依赖旧值时请使用 setX(prev => ...)
  • 不可变更新是铁律:对象/数组要创建新的替换,嫌麻烦可以使用 Immer。
  • 惰性初始化 useState(() => ...) 用于处理昂贵的初始值。
  • 复杂状态逻辑使用 useReducer 集中管理。

下一篇将介绍副作用与生命周期 —— watch/watchEffect/onMounted 如何映射为 useEffect——它一个 Hook 就能统一你熟悉的 watchwatchEffectonMountedonUnmounted

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。