主题
状态管理
React 的组件状态管理主要依托于内置的 useState Hook。因此,深入理解并掌握 useState 的核心用法是构建交互组件的关键。
基本使用
tsx
import React, { useState } from 'react'
function Counter() {
// 声明状态:count 是当前值,setCount 是修改它的函数
const [count, setCount] = useState(0) // 初始值设为 0
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>当前计数:{count}</h2>
{/* 更新方式1:直接更新状态 */}
<button onClick={() => setCount(count + 1)}>直接更新 +1</button>
{/* 更新方式2:使用函数式更新(处理连续点击或复杂逻辑更安全) */}
<button onClick={() => setCount((prevCount) => prevCount + 1)}>函数式更新 +1</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
)
}
export default Counter计算属性
在 React 中,并没有名为 computed 的内置 API。这是因为在函数组件内部,任何声明在 return 之前的变量,在每次渲染时都会根据最新的 state 或 props 重新计算。
tsx
import { useState } from 'react'
const ShoppingCart = () => {
const [items, setItems] = useState([
{ id: 1, name: 'Apple', price: 10 },
{ id: 2, name: 'Banana', price: 5 },
])
// 这里的 totalPrice 就像 Vue 的计算属性
// 只要 items 改变,组件重新渲染,它就会自动更新
const totalPrice = items.reduce((acc, item) => acc + item.price, 0)
return <div>总价: {totalPrice}</div>
}tsx
/*
如果计算逻辑非常耗时(例如处理上万条数据的排序或搜索),或者该计算结果作为 props 传递给子组件导致了多余渲染,
则需要使用 `useMemo` 进行“记忆化”。
*/
import { useState, useMemo } from 'react'
const DataList = ({ data }) => {
const [filterText, setFilterText] = useState('')
// 只有当 data 或 filterText 改变时,才会重新运行过滤逻辑
const filteredData = useMemo(() => {
console.log('执行了高耗时过滤...')
return data.filter((item) => item.includes(filterText))
}, [data, filterText]) // 依赖数组
return <input value={filterText} onChange={(e) => setFilterText(e.target.value)} />
}注意事项
- 不要过度使用 useMemo: 简单的加减乘除或数组操作,直接写在函数体内即可。useMemo 本身也有缓存开销,盲目使用反而会降低性能。
- 避免在计算属性中触发 Side Effects: 计算逻辑应该是“纯函数”。如果你发现自己在计算过程中修改了其他状态(例如在里面调用 setCount),请改为使用 useEffect。
- 单向数据流思维: 记住 React 的核心公式:
UI = f(state)。计算属性本质上是基于现有 state 推导出来的“衍生状态”,不要把它们存入新的 useState 中,否则会引发同步困难。
常见问题
依赖于旧值的状态更新
useState 的更新方式有两种:
- 直接更新:适用于新状态不依赖于旧状态的情况。
- 函数式更新:适用于新状态依赖于旧状态的情况,
tsx
// ❌ 错误示例:
// 最终结果:count 只增加了 1
const handleClick = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
// ✅ 正确示例
// 最终结果:count 增加了 3
const handleClick = () => {
setCount((prevCount) => prevCount + 1)
setCount((prevCount) => prevCount + 1)
setCount((prevCount) => prevCount + 1)
}提示
- 闭包陷阱:
handleClick函数作用域内的count值,永远是当前渲染时的“快照”(0)。 - 异步与批处理:React 会将短时间的多次
setCount调用合并处理,以此来优化性能。
“对象/数组” 的状态更新
在 React 中,初学者最常犯的错误是直接修改 state(Mutation)。
tsx
// ❌ 错误:直接修改对象属性(Mutation)
const updateName = () => {
user.name = 'Bob' // 虽然 user 内容变了,但引用地址没变
setUser(user) // React 对比旧 user 和新 user 的地址,发现一致,不渲染
}
// ✅ 正确:保持不可变性(Immutability)
const updateName = () => {
// 使用展开运算符创建一个全新的对象(新地址)
setUser({ ...user, name: 'Bob' })
}setState 的更新是异步的吗?
结论:调用过程是同步的,但生效过程是“异步”的(延迟处理)。
- 批处理机制(Batching):React 会将同一个事件处理函数(如 onClick)中的多次 setState 合并。这就像你去餐厅点餐,服务员会等你说完所有的菜品再一次性交给后厨,而不是点一个菜跑一趟。
- 快照(Snapshot)概念:每次渲染都有它自己的 Props 和 State。在当前函数体逻辑执行完之前,state 的值始终是本次渲染开始时的“快照”。
