这次把React中的异步更新状态搞明白

为什么说React中状态更新可能是异步的?

在React中,状态更新被描述为可能是异步的,主要是指在多个setState调用的情况下,React可能会延迟状态更新或将它们合并为单个更新,来优化性能和响应速度。这一机制是React用来提升其效率和响应能力的。

举个例子:

react 复制代码
const [count, setCount] = useState(0)

useEffect(() => { 
    setCount(count + 1) 
    setCount(count + 1) 
    setCount(count + 1) 
}, [])

在这段的代码中,count的值只会增加 1 ,useEffect仅在组件挂载时运行一次,即为一个渲染周期 。由于每一次调用setCount(count + 1)都是基于相同的初始count值来计算的,所以只有一次有效的增加。原因在于React的状态更新(特别是使用同一个状态更新函数时,如setCount)是异步且合并的。这意味着在一个渲染周期内,所有对同一状态的更新会被合并并计算为一个更新。

那么就可以认为,这里的状态更新与setState并没有关系,而是与state有关系。因为在一个渲染周期内,state的值是相同的。在同一个渲染周期(或者说在当前的函数调用事件处理函数 、或是生命周期方法 的执行过程中)里,不论你调用了多少次setState,所有这些setState都是基于同一个state的原始值进行计算的。

如果你连续多次直接以state的当前值去计算下一个state(比如setState(count + 1)),实际上每次计算得到的值都是基于同一个count值,因此这些更新最终会被合并,只有一次效果会被应用。

那么在同一个渲染周期中,如何同步更新state值:

要解决这个问题,确保每次setState都基于最新的state值,需要使用函数式的setState更新(即setState(prevState => prevState + 1))。这种方式每次都会接收到最新的state值作为参数,无论之前进行了多少次更新,每次函数都会基于最近更新后的state值计算新的值:

react 复制代码
const [count, setCount] = useState(0)

useEffect(() => { 
    setCount(pre => pre + 1) 
    setCount(pre => pre + 1) 
    setCount(pre => pre + 1) 
}, [])

在这种情况下,每次更新都是基于状态的最新值,因此count会增加3。

因此,在一个渲染周期内state的值是不变的这一事实,而非setState本身的机制,导致了只有基于最初的state值的一次有效计算。这解释了为何在同一个渲染周期中,基于state的直接更新可能不会如预期那样累加更新。

为什么在一个渲染周期中,不论同步执行多少次state,的值都是相同的呢?

因为在调用setState方法更新状态时,React会将这个更新操作排入一个队列中,并不会立即执行这个操作。只有当当前的执行栈清空,React才会从队列中取出所有的更新操作,并开始重新渲染组件。在这个过程中,React会尝试合并多个状态更新,以减少不必要的渲染次数。这就意味着,在一个渲染周期中触发的所有状态更新,实际上只会引起一次重新渲染,而在这次重新渲染发生之前,状态的值都不会发生变化。

这一机制是React用来提升其效率和响应能力的,React为什么这样做呢?

  1. 性能优化 :React通过批处理状态更新来减少不必要的渲染次数。如果每次调用setState都会立即触发组件的重新渲染,那在处理大量状态更新时,应用的性能会受到严重影响。通过将多个更新合并成单个更新,React可以减少渲染次数,从而提高性能。

  2. 一致性保障:在事件处理、生命周期方法或钩子(Hooks)中,React保证了状态的一致性。在这些情况下,React会延迟状态更新的应用,直到所有处理函数执行完毕。这确保了在任何给定时刻,组件的状态都是一致的,并且避免了在短时间内由于状态不一致而产生的潜在错误。

  3. 避免不必要的计算和渲染:通过异步状态更新和批处理,React能够合并多次状态更新,避免了因状态的微小变化而导致的整个组件树的不必要重新计算和渲染。这意味着只有当所有状态更新完成后,React才计算最终状态,并基于此状态重新渲染UI,从而提高了应用的效率。

要注意的是,尽管React中的状态更新可能是异步的,但这并不意味着它们是在JavaScript事件循环的下一个tick中执行的。这里的"异步"是相对于代码的执行流程而言的,意味着React可能会暂时延迟更新操作,而不是立即执行。

相关推荐
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java2 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele2 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
DOKE3 小时前
VSCode终端:提升命令行使用体验
前端
xgq3 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081353 小时前
前端之路-了解原型和原型链
前端
永远不打烊3 小时前
librtmp 原生API做直播推流
前端