递归中状态未更新
export default function App() {
const [actionId, setActionId] = useState("");
const startTest = async () => {
const newActionId = actionId + 1;
setActionId(newActionId);
const request = {
actionId: newActionId
}
console.log({ request });
// const response = await api.runTests(request)
await new Promise((resolve) => setTimeout(resolve, 4000));
startTest();
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={startTest}>Start</button>
</div>
);
}
request
actionId 始终为 1,尽管我每 4 秒更改一次。
我知道
setState
是异步的,但奇怪的是 4 秒后状态没有更新。
理论
渲染的 React 组件包含属于
该
渲染的函数。如果这些函数引用了过时的变量,则会影响结果。搜索
过时的闭包
以了解有关此内容的更多信息。在 React 中处理传统 Javascript 函数
setTimeout
和
setInterval
时,特别容易出现过时的闭包。
那么发生了什么?
因此,在第一次渲染时,存在一个特定的
startTest
实例。单击按钮时,将运行该
startTest
实例。
startTest
的这个实例包含一个
actionId
闭包(顺便问一下,为什么要将
actionId
设置为
""
,然后对其执行加法?更期望从 0 开始,或者通过
+ "1"
执行加法)。设置状态后,React 会重新渲染,将
actionId
设置为
"1"
,并使用新版本的
startTest
重新渲染,其中包含一个闭包,其中
actionId
为
"1"
。但是,只有再次单击按钮时才会触发此函数。相反,第一次渲染的超时会触发对第一次渲染的
旧
startTest
的新调用。此超时不知道组件已重新渲染,并且其他地方有新版本的
startTest
,其中更新了
actionId
闭包。当重新触发该函数时,它会计算与第一次相同的
newActionId
值,因此我们陷入了一个循环,该循环仅使用包含过时闭包的
startTest
的第一个实例。
如何解决?
如果您想在 React 中使用超时和间隔,您必须按照 React 的方式进行。可能有适合此目的的软件包,但如果您愿意,可以稍微更改示例以使其正常工作。
您可以向
setActionId
提供一个函数,该函数将前一个值作为输入,而不是计算
newActionId
的值:
setActionId(oldActionId => oldActionId + 1)
现在,由于前一个值始终作为输入传递,因此过时的闭包不会影响函数的结果,您的示例将正常工作。但是,我不确定设计是否正确,因为如果用户再次按下按钮,您将同时运行多个超时,从长远来看会造成严重破坏。尽管如此,如果您想确保它按预期工作,您可以尝试这种方式。如果您可以通过限制按钮只能按一次来保证该函数只执行一次,那么这将是一个更好的设计。
祝你好运!