异步函数不断读取全局变量的旧值



我有以下 React 组件

const [prjToFetch, setPrjToFetch] = React.useState([]);
React.useEffect(() => {
console.log("Before " + prjToFetch)
async function func(){
let myPromise = await new Promise(function(myResolve, myReject) {
setTimeout(function() { myResolve("I love You !!"); }, 3000);
});

console.log("After: " + prjToFetch)
return myPromise;
}
func()
}, [prjToFetch])

我有一个按钮,每当按下值"a"时,它都会将值"a"推入数组 prjFetch:

const handleClick = () => {
setPrjToFetch([...prjToFetch, "a"]);
}

如果我按一次按钮,前面代码的输出是:

Before a
(3 seconds of idle time)
After a

这正是我所期望的。但是假设我连续按两次按钮,那么输出是:

Before a
Before a a
(3 second of idle time)
After a
After a a

为什么第一个"After"只返回"a"而不是"a a"?

请注意,我还尝试将代码重写为:

React.useEffect(() => {
console.log("Before " + prjToFetch)
async function func(){
await new Promise(function(myResolve, myReject) {
setTimeout(function() { myResolve("I love You !!"); }, 3000);
});
}
func().then(() => {
console.log("After: " + prjToFetch)
})
}, [prjToFetch])

但结果是一样的

问题是func创建func时关闭了prjToFetch的值,并且您正在异步使用该值。当func使用该值时,它已经过时了。

以下是正在发生的事情:

  1. 组件挂载,触发对useEffect回调的调用。
    1. 这将创建一个func的副本,该副本在当前prjToFetch变量上关闭。
    2. 它称之为func
    3. func开始等待三秒钟
  2. 单击该按钮,触发prjToFetch的状态设置器
  3. 这使得组件函数再次运行,创建一组新的状态变量。由于prjToFetch发生了变化,React 再次调用您的useEffect回调
    1. 这将创建一个新func,该关闭新的prjToFetch变量
    2. 它称之为func
    3. func开始等待三秒钟
  4. 第一个func完成等待,并显示它关闭的prjToFetch的值
  5. 第二个func完成等待,并显示关闭的prjToFetch的值(与前一个关闭func的值不同)

它和这个完全一样:

let aInComponentState = 1;
function componentFunction() {
const a = aInComponentState;
console.log(`before: ${a}`);
setTimeout(() => {
console.log(`after: ${a}`);
}, 1000);
}
componentFunction(); // component function called for mount
++aInComponentState; // button click updates state
componentFunction(); // component function called again because of state update

您可以从useEffect回调返回一个函数,以便它知道组件已在上面的步骤 2 和 3 之间重新呈现。

真正的解决方案会根据你实际想要做的事情而有所不同。但是如果你想让func始终看到当时的当前值prjToFetch,并且你希望两个func实例都运行,你需要使用setPrjToFetchsetter 来访问当前值(这有点黑客):

setPrjToFetch(currentPrjToFetch => {
console.log("After: " + currentPrjToFetch);
});

同样,这有点笨拙,通常有更好的解决方案更具体地针对您实际使用prjToFetch的目的。


我建议阅读丹·阿布拉莫夫(Dan Abramov)的《useEffect完全指南》。它可以帮助您理解useEffect,但也可以帮助您了解一般的钩子。

最新更新