useState 深入:从入门到不踩坑
好,咱们今天聊聊 useState。这个 Hook 可以说是 React 状态管理的基石。我刚开始用 React 的时候,觉得它不就是个「存数据」的东西嘛,后来踩了不少坑才明白——嗯,这里面的门道其实挺多的。
一、基本用法:你真的会用吗?
先看个最简单的例子:
const [count, setCount] = useState(0);
这行代码做了三件事:
- 声明了一个状态变量
count,初始值是 0 - 返回了一个更新函数
setCount - React 会记住这个状态,组件重新渲染时不会丢失
我个人习惯把状态变量名和 set 函数名对应起来,比如 user 对应 setUser,list 对应 setList。这样代码读起来很顺,团队协作时别人也一眼能看懂。
二、状态更新的异步性:为什么 setCount 后立刻打印还是旧值?
这个问题我至少被问过十次。来看代码:
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // 输出:0,而不是 1
}
为什么会这样?
说白了,setCount 不是立即执行的。React 会把状态更新放进一个队列里,等到合适的时机(比如事件处理函数执行完毕)再批量处理。你想想看,如果每次 setState 都立刻触发重新渲染,那性能得多差?
我在项目中遇到过这样一个场景:用户提交表单时,我需要先更新一个 loading 状态,然后发起请求。如果我在 setLoading 之后立刻判断 loading 的值,那肯定出问题。正确的做法是依赖 useEffect 或者在回调里处理。
三、函数式更新:解决「依赖旧值」的利器
刚才说到 setCount 是异步的,那如果连续调用两次呢?
function handleClick() {
setCount(count + 1);
setCount(count + 1);
}
// 结果:count 只增加了 1,而不是 2
这是因为两次 setCount 都基于同一个旧的 count 值。解决办法就是函数式更新:
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}
// 结果:count 增加了 2
函数式更新的参数 prev 是上一次更新后的最新值,而不是当前渲染时的值。这样就能保证每次更新都基于正确的结果。
我曾经在做一个购物车功能时,用户连续点击「加一」按钮,如果用普通更新,数量总是少算。改成函数式更新后,问题立刻解决了。嗯,这个坑我印象很深。
四、惰性初始化:别让计算浪费性能
useState 的初始值可以是一个函数,这个函数只会在组件首次渲染时执行一次。这就是惰性初始化。
// 不好的写法:每次渲染都会计算
const [data, setData] = useState(expensiveComputation());
// 好的写法:只在首次渲染时计算
const [data, setData] = useState(() => expensiveComputation());
你想想看,如果 expensiveComputation 是个很耗时的操作(比如解析大量数据、读取 localStorage),那每次组件重新渲染都执行一遍,页面不卡才怪。
我有个同事曾经在项目里用第一种写法,结果列表页每次滚动都卡顿。排查了半天,发现是 useState 的初始值计算拖了后腿。改成惰性初始化后,性能直接翻倍。
- 初始值需要复杂计算(循环、递归、数据处理等)
- 初始值来自 localStorage 或 sessionStorage
- 初始值需要调用 API(虽然我更推荐在 useEffect 里做)
五、总结一下
useState 看似简单,但用好它需要理解三个关键点:
| 知识点 | 核心要点 | 常见坑 |
|---|---|---|
| 基本用法 | 声明状态、获取更新函数 | 对象/数组更新时忘记创建新引用 |
| 异步更新 | setState 是批量异步执行的 | setState 后立刻读取状态值 |
| 函数式更新 | 基于上一次状态计算新状态 | 连续更新时依赖旧值 |
| 惰性初始化 | 初始值函数只执行一次 | 每次渲染都执行复杂计算 |
说实话,这些知识点我刚开始学的时候也觉得「哦,知道了」。但真正写项目时,每个坑都踩过一遍才真正理解。所以我的建议是:别光看,动手写。写个计数器、写个购物车、写个表单,把函数式更新和惰性初始化都用上。用着用着,你就懂了。
好,这一章就到这儿。下一章咱们聊聊 useEffect,那又是一个容易踩坑的地方。