# React 中的 setState

3 min read
Table of Contents

在 React 中,setState 是同步,还是异步在不同的版本有不同的表现。

React 16

setState 在不同的情况下可以表现为异步或同步。

在 Promise 的状态更新、JS 原生事件,setTimeout、setInterval 中是同步的。

在 React 的合成事件中,是异步的。

原因

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 state。

但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 state。

ReactFiberScheduler.js
let isBatchingUpdates = false
function batchedUpdates(fn, a) {
const previousIsBatchingUpdates = isBatchingUpdates
isBatchingUpdates = true
try {
return fn(a)
} finally {
isBatchingUpdates = previousIsBatchingUpdates
if (!isBatchingUpdates && !isRendering) {
performSyncWork()
}
}
}

React 18

setState 都会表现成异步(即批处理)

可以查看官方的讨论 Automatic batching for fewer renders in React 18

批量更新

在 react 里面一个典型的例子就是连续设置 state:

import { useState } from 'react'
export default function Counter() {
const [number, setNumber] = useState(0)
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setNumber(number + 1)
setNumber(number + 1)
setNumber(number + 1)
}}
>
+3
</button>
</>
)
}

虽然调用 setNumber(number + 1) 三次,但是每次事件处理中 number 总是 0,相当于设置了 number 为 1 设置了三次。所以最终渲染的时候 number 是 1 而非 3。

在一次事件中去多次更新 state,可以使用 setNumber(n => n + 1) 的更新函数。

参考链接


More Posts

# React 中的 Ref

5 min read

组件被调用时会新建一个实例,ref 可以指向该实例,如果是原生 DOM 组件,我们可以得到 DOM 节点。

# TypeScript中的数据类型

3 min read

typescript 拥有 javascript 相同的数据类型,还提供了更多其他的实用的类型使用。如常见的 tuple(元组),enum(枚举),any(任意值),unknow(未知),void(空值)和 never(从不)。