useState 深入:从入门到不踩坑

好,咱们今天聊聊 useState。这个 Hook 可以说是 React 状态管理的基石。我刚开始用 React 的时候,觉得它不就是个「存数据」的东西嘛,后来踩了不少坑才明白——嗯,这里面的门道其实挺多的。

一、基本用法:你真的会用吗?

先看个最简单的例子:

const [count, setCount] = useState(0);

这行代码做了三件事:

  • 声明了一个状态变量 count,初始值是 0
  • 返回了一个更新函数 setCount
  • React 会记住这个状态,组件重新渲染时不会丢失

我个人习惯把状态变量名和 set 函数名对应起来,比如 user 对应 setUserlist 对应 setList。这样代码读起来很顺,团队协作时别人也一眼能看懂。

小技巧:useState 可以接受任意类型的初始值——数字、字符串、对象、数组,甚至函数。但要注意,如果是对象或数组,更新时要保证传入的是新引用,否则 React 可能不会触发重新渲染。

二、状态更新的异步性:为什么 setCount 后立刻打印还是旧值?

这个问题我至少被问过十次。来看代码:

const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);
  console.log(count); // 输出:0,而不是 1
}

为什么会这样?

说白了,setCount 不是立即执行的。React 会把状态更新放进一个队列里,等到合适的时机(比如事件处理函数执行完毕)再批量处理。你想想看,如果每次 setState 都立刻触发重新渲染,那性能得多差?

我在项目中遇到过这样一个场景:用户提交表单时,我需要先更新一个 loading 状态,然后发起请求。如果我在 setLoading 之后立刻判断 loading 的值,那肯定出问题。正确的做法是依赖 useEffect 或者在回调里处理。

注意:千万不要在 setState 之后立即读取状态值。如果你需要基于最新状态做操作,要么用 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,那又是一个容易踩坑的地方。