针对于这个问题, 咱们能够先看一段代码
function handleClick() {
console.log(count); // 0
setCount(count + 1); // 请求运用 1 重新烘托
console.log(count); // 仍然是 0!
setTimeout(() => {
console.log(count); // 仍是 0!
}, 5000);
}
这边React给的回答是: set函数不能改变运转中代码的状况 , 更新状况会运用最新的值请求另一个烘托, 但并不影响已经运转的事情处理函数中的变量
OK针对于上面这句话咱们来做一个简略的回答: 便是说React会用新的state值去请求新的一个烘托, 但你正在运转的函数中的值不会受到影响
React会用新的state值去请求新的一个烘托
针对于上半句话咱们来看一段代码
import { useState } from 'react';
export default function Form() {
const [isSent, setIsSent] = useState(false);
if (isSent) {
return <h1>Your message is on its way!</h1>
}
return (
<form onSubmit={(e) => {
e.preventDefault();
setIsSent(true);
sendMessage(message);
}}>
<button type="submit">Send</button>
</form>
);
}
function sendMessage(message) {
// ...
}
假如对这段代码进行运转, 那么咱们能够了解react回答的前半句, 当咱们更新state时, 他会用最近的值去请求一个烘托, 也便是他的dom展示会变成<h1>Your message is on its way!</h1>
这边咱们再引出另外一个知识点: state表现出来的特性更像是一张快照, 当组件被调用时, 组件会为那一次烘托提供一个state快照, 然后组件会根据这个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>
</>
)
}
这个组件的运转成果终究是: 每点击一次, number的数量并不是+3而+1, 这个其实就解说了, 在该次烘托前, state提供给组件的烘托快照中的值是0 , 每一次set, 都是在上一次快照的基础上+1, 那么每一个set函数最后呢执行出来的成果都是1, 所以下一次给到烘托的state值也便是0+1, 循环往复每一次点击都是在上一次快照的基础上+1
接下来咱们再去解说后半句
正在运转的函数中的值不会受到影响
已知紧跟在set函数后边输出state值不会是更新后的值, 那么咱们给他加一个定时器让他晚一点再输出, 说不定晚点输出后他就更新完了呢?
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number);
}, 3000);
}}>+5</button>
</>
)
}
这段代码的运转成果:UI中烘托的值立刻变成了 5 , 定时器结束后页面弹出 0
那么咱们持续添加定时器的时刻后, 其实能够发现, 异步并不能处理咱们的问题, 这个0似乎是运转这个办法时就已经决定了他是0, 也便是说, 一个state变量的值永远不会在一次烘托的内部发生变化, 这其实也就解说了后半句, 正在运转中的函数不会受影响, 既然这一次运转中的state都不会发生变化, 那么也天然就不会更新了
了解了原因和运转机制后, 咱们再来看看怎么去处理这个问题:
假如咱们需求运用下一个状况, 能够将他传递给set函数之前保存在一个变量中, 再将保存的变量传递给set函数, 也便是用来一个变量来保持同样的值运用
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
OK , 到这儿咱们其实已经知道了标题问题的原理和处理方案, 那么咱们再看一个引申的问题
在上面的代码中, 有一段代码是对state做了三次setNumber(number + 1);
的操作, 最后的成果并没有变成+3而是+1
那么咱们怎么处理在一次事情更新同一个state进行多次更新呢, 比如+1+1+1这样, 最简略的办法当然是一步到位直接+3, 这是一个不常见的用例, 但是这一块其实能够引出一个批处理的概念
React会比及事情处理函数中的所有代码都运转完毕后再处理state更新
光看这段或许不是很好了解, 咱们参照上面的代码来看
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}} />
在上述代码中, react会形成一个行列, 以便在事情处理函数中的其他事情运转完毕后再去处理state操作, 也便是这边行列中会寄存三个
setState(0+1)
那么咱们假如想让这个行列变成
setState(0+1)
setState(1+1)
setState(2+1)
也便是咱们终究预期的+3成果, 咱们需求借助一个更新函数
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
这个时分react放进更新行列中的就不是0+1
而是n => n + 1
, 他将0作为入参传入第一个更新函数, 得到0+1
也便是1
, 然后将上一个更新函数的返回值作为入参传递给下一个更新函数也便是1+1
, 以此类推咱们能够终究实现咱们的+3的需求. 但是这边建议直接用setState(number+3)
. 当然这只是一个简略case下的实现, 假如有更杂乱硬要拆分的需求时再运用更新函数来完成这个操作