React中useState hook的运行机制和常见useState更新问题
在最开始使用useState的时候会遇到这样的一个疑惑问题,比如下面的代码:
疑问🤔 :为什么ceshi只加了1,明明设置了三次加1,ceshi显示的是1,而不是3。之前对于这里只是知道一点异步之类的。
但是具体原因是什么一直没有去搞懂,这次梳理了一下useState的执行逻辑和常见问题解答,看完一定可以让你明白useState不再疑惑。
export default memo(function TalentBox() {
const [ceshi,setceshi] = useState(0)
return (
<TalentBoxWrapper>
{ceshi} //显示 1,而不是一次性加了3
<button onClick={()=>{
setceshi(ceshi+1)
setceshi(ceshi+1)
setceshi(ceshi+1)
}}>132</button>
<div className="dss-talent-box-left">1</div>
<div className="dss-talent-box-right">2</div>
</TalentBoxWrapper>
)
})
在React中,useState
的状态更新是异步的,并且在同一个事件处理函数中多次调用状态更新函数(如setceshi
)时,React会将这些更新合并为一次更新,这意味着在同一个时间循环中多次调用setceshi
,只有最后一次调用的结果
会生效。因此,在上述代码中,虽然调用了三次setceshi(ceshi + 1)
,但实际上它们会被合并为一次更新,只有最后一次setceshi(ceshi + 1)
的调用结果会生效。(注意⚠️
:只有最后一次的调用结果生效了,并不是说只有最后一次的setceshi(ceshi + 1)
执行了,setceshi(ceshi + 1)
每次都执行了。)
要解决这个问题并确保每次点击按钮时ceshi
增加3,可以使用函数式的状态更新方法。这样可以确保每次更新都基于最新的状态值。修改后的代码如下:
const [ceshi, setceshi] = useState(0);
return (
<TalentBoxWrapper>
{ceshi}
<button onClick={() => {
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
}}>132</button>
</TalentBoxWrapper>
);
在这个例子中,prevCeshi
是前一个状态值,每次调用setceshi
时都会基于最新的状态值进行更新,因此每次点击按钮时ceshi
会增加3。为什么函数是可以得到最新累加值的,原因在下面⬇️⬇️⬇️。
为什么函数方式的调用可以保证每次都是最新的值可以实现多次累加
在React中,使用函数式的状态更新可以确保每次更新都基于最新的状态值。这是因为函数式更新接收一个函数作为参数,这个函数的参数是当前的状态值,而不是闭包中的旧状态值。
具体来说,当你使用函数式更新时,React会确保传递给这个函数的参数是最新的状态值,即使在同一个事件处理函数中多次调用状态更新函数。这样可以避免由于闭包导致的状态值不一致问题。
让我们详细看看为什么函数式更新可以实现多次累加:
闭包问题
在你的原始代码中:
<button onClick={() => {
setceshi(ceshi + 1);
setceshi(ceshi + 1);
setceshi(ceshi + 1);
}}>132</button>
每次调用setceshi(ceshi + 1)
时,ceshi
的值都是事件处理函数开始执行时的值(即闭包中的值)。由于React的批处理机制,这些更新会被合并,最终只会增加一次。
函数式更新
使用函数式更新可以避免这个问题:
<button onClick={() => {
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
setceshi(prevCeshi => prevCeshi + 1);
}}>132</button>
在这个例子中,每次调用setceshi
时,传递给更新函数的prevCeshi
都是最新的状态值。具体过程如下:
- 第一次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是当前的状态值,比如0。 - React会将这个更新放入队列,并在适当的时候处理它。
- 第二次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是第一次更新后的状态值,比如1。 - 第三次调用
setceshi(prevCeshi => prevCeshi + 1)
时,prevCeshi
是第二次更新后的状态值,比如2。
这样,每次更新都是基于最新的状态值,因此最终的状态值会正确地累加3。
总结
函数式更新通过确保每次更新都基于最新的状态值,避免了闭包导致的状态值不一致问题,从而实现了多次累加。setState
本身不是宏任务或微任务,而是由React的调度系统管理的。React会在合适的时机批量处理这些状态更新,以优化性能。