众所周知,React 提供的 setState() 是一个“异步”执行的函数,为什么要加引号呢?因为 setState() 并不是真正意义上的异步函数,这里的“异步”指的是把多个 setState 合并到一起进行一次性的更新,而不是像 setTimeout() 这类的函数。下面我们先来看一个例子:
例1:
export default function App() {
const [state, setState] = useState(0)
const add3 = () => {
setState(state+1)
setState(state+1)
setState(state+1)
}
return (
<div className="App">
<button onClick={add3}>+3</button>
<p>{state}</p>
</div>
);
}
- 请问这段代码中,点击 +3 的按钮,最后 state 会加多少呢?
- 答:+1
- 原因:React 会把 setState() 合并为一次批量更新,那么这三个 setState() 会被合并为一个 setState() 而其中,state 是没有发生变化的,三个 setState() 中的 state 的值都是 0,所以每次点击都只能完成 +1 的操作,而不是想要的 +3 的操作
那么有时候会遇到这样的情况:有一个 input,当 input 框内的值变化时,想要把其中的值 log 出来
export default function App() {
const [state, setState] = useState(0)
return (
<div className="App">
<input onChange={(value) => {
setState(value.target.value)
console.log(state)
}}/>
<p>{state}</p>
</div>
);
}

- console.log() 的结果与输入的值不一致,每次打出的值都会落后一步
- 这也是因为 setState() 的“异步”导致的,setState() 在接受一个值后,并不会立刻去改变其中的 state,而是需要去比较前后变化的内容最后再去更新 UI,可以理解为是很耗时间的事情(相对于 console.log 来说)
React 自己封装的 onChange、onClick 等模块中,使用 setState() 都会出现这样的”异步”的情况,想要避免异步,让 setState() 变为同步更新,有下面几种方法
1、不使用 React 自带的 onClick 等方法,而使用原生 js 的 addEventListener 来添加事件监听函数
export default function App() {
const [state, setState] = useState(0)
const add3 = () => {
setState(state => state+1)
setState(state => state+1)
setState(state => state+1)
}
useEffect(() => {
const btn = document.getElementById('btn')
btn.addEventListener('click', add3)
},[])
return (
<div className="App">
<button id="btn">+3</button>
<p>{state}</p>
</div>
);
}
3、使用函数式来调用 setState()
export default function App() {
const [state, setState] = useState(0)
const add3 = () => {
setState(state => state+1)
setState(state => state+1)
setState(state => state+1)
}
return (
<div className="App">
<button onClick={add3}>+3</button>
<p>{state}</p>
</div>
);
}
目前遇到这样的需求,在点击按钮 +3 后,a 执行 +3 的操作,同时需要把 b = a*100。理想情况是,点击后,a = 3,b = 300,a = 6, b = 600 。但是结果往往不如人意,这时候出现了一个延迟的问题
例2:
export default function App() {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const add3 = () => {
setA(a => a+1)
setA(a => a+1)
setA(a => a+1)
setB(b => a*100)
}
return (
<div className="App">
<button onClick={add3}>+3</button>
<p>{a}</p>
<p>{b}</p>
</div>
);
}

- b 的值的增长永远比 a 慢一步,与预想中,b = a*100 的情况不一样,b = 上一次 a 的值 *100
- 由此得出结论,在一个函数中,不能使用 setState 获取到的值进行相互计算
例3:
export default function App() {
const [a, setA] = useState(1)
const [b, setB] = useState(1)
const add3 = () => {
setB(b => 10)
setA(a => a*b)
}
return (
<div className="App">
<button onClick={add3}>+3</button>
<p>{a}</p>
</div>
);
}
- 上面的代码同样存在一个明显的 bug,当第一次点击 +3 的按钮后,a 的值并不会 *3,而是 1,这就是和上面的问题一样,存在一个延迟,第二次再点击 a 才会 *3
- 要解决这个问题就简单一些,只需要把
setB(b => 10)
放到外部就可以了
export default function App() {
useEffect(() => {
setB(b => 10)
}, [])
const [a, setA] = useState(1)
const [b, setB] = useState(1)
const add3 = () => {
setA(a => a*b)
}
return (
<div className="App">
<button onClick={add3}>+3</button>
<p>{a}</p>
</div>
);
}