先说结论:本篇是整个系列中最核心的内容。Vue 的响应式(ref / reactive)是其灵魂,而 React 中与之对应的核心是 useState。但两者背后的机制完全不同——理解这一差异,你才算真正“懂” React。 一、useState vs ref:第一印象 复制代码// Vue3 co
先说结论:本篇是整个系列中最核心的内容。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]。
核心差异:
count 是一个响应式容器,你修改 .value,Vue 会精准更新用到它的地方。count 只是本次渲染中的一个普通数值快照;调用 setCount 并非“改变量”,而是告诉 React“用新值重新渲染整个组件”。回顾第 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>
}
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 的 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>
)
}
选择建议:
useStateuseReducer| 需求 | 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(请求渲染),不能直接赋值。setX(prev => ...)。useState(() => ...) 用于处理昂贵的初始值。useReducer 集中管理。下一篇将介绍副作用与生命周期 —— watch/watchEffect/onMounted 如何映射为 useEffect——它一个 Hook 就能统一你熟悉的 watch、watchEffect、onMounted、onUnmounted。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述