我有以下 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
使用该值时,它已经过时了。
以下是正在发生的事情:
- 组件挂载,触发对
useEffect
回调的调用。- 这将创建一个
func
的副本,该副本在当前prjToFetch
变量上关闭。 - 它称之为
func
- 该
func
开始等待三秒钟
- 这将创建一个
- 单击该按钮,触发
prjToFetch
的状态设置器 - 这使得组件函数再次运行,创建一组新的状态变量。由于
prjToFetch
发生了变化,React 再次调用您的useEffect
回调- 这将创建一个新
func
,该关闭新的prjToFetch
变量 - 它称之为
func
- 该
func
开始等待三秒钟
- 这将创建一个新
- 第一个
func
完成等待,并显示它关闭的prjToFetch
的值 - 第二个
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
实例都运行,你需要使用setPrjToFetch
setter 来访问当前值(这有点黑客):
setPrjToFetch(currentPrjToFetch => {
console.log("After: " + currentPrjToFetch);
});
同样,这有点笨拙,通常有更好的解决方案更具体地针对您实际使用prjToFetch
的目的。
我建议阅读丹·阿布拉莫夫(Dan Abramov)的《useEffect
完全指南》。它可以帮助您理解useEffect
,但也可以帮助您了解一般的钩子。