[英]React state inside a function is not changing even after calling it with a delay of (5 seconds)
在反应中,我正在使用功能组件,并且有两个函数 (getBooks) 和 (loadMore)
getBooks
从getBooks
获取数据。 但是,当我在getBooks
函数 (loadMoreClicked) 内的按钮单击上调用loadMore
函数时,即使在延迟(5 秒)调用它之后,它也不会更改它使用以前的状态。 但是当我再次调用loadMore
时,状态会发生变化并且一切正常。
有人可以解释为什么最初调用 (getBooks) 时的 (loadMoreClicked) 甚至在延迟 5 秒后调用它也没有更新。
function component() {
const [loadMoreClicked, setLoadMore] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`; //this is my end point
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)
setTimeout(() => {
getBooks(); // i am calling (getBooks()) after 5 seconds.
}, 5000);
};
return (
<div>
<button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
function
</div>
);
}
有两件事正在发生:
getBooks()
是使用const
在周围功能中定义的值。 当函数在其定义之外引用const
或let
变量时,它会创建所谓的闭包。 闭包从这些外部变量中获取值,并在构建函数时为内部函数提供值的副本。 在这种情况下,该函数是在最初调用状态后立即构建的,并将loadMoreClicked
设置为false
。
那么为什么setLoadMore(true)
触发重新渲染并重写函数呢? 当我们设置状态时,重新渲染不会立即发生。 它被添加到 React 管理的队列中。 这意味着,当执行loadMore()
时, setLoadMore(true)
表示“在我运行完其余代码后更新状态”。 重新渲染发生在函数结束之后,因此使用的getBooks()
副本是在此循环中构建和排队的副本, getBooks()
内置了原始值。
对于您正在执行的操作,您可能希望在超时时调用不同的函数,具体取决于按钮是否被单击。 或者,您可以创建另一个更直接的闭包,具体取决于您是否希望getBooks()
考虑单击按钮,如下所示:
const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
() => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
})
.catch(err => {
console.log(err);
});
}
...
const loadMore = () => {
setLoadMore(true);
setTimeout(
getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
5000
);
};
还有第三个选项,即将const [loadMoreClicked, setLoadMore]
重写为var [loadMoreClicked, setLoadMore]
。 虽然引用const
变量会在那一刻冻结值,但var
不会。 var
允许函数动态引用变量,以便在函数执行时确定值,而不是在定义函数时确定。
这听起来像是一个快速而简单的解决方法,但在闭包中使用时可能会导致混淆,例如上面的第二个解决方案。 在这种情况下,由于闭包的工作方式,该值再次固定。 因此,您的代码会将值冻结在闭包中,但不会冻结在常规函数中,这可能会导致更多的混乱。
我个人的建议是保留const
定义。 var
在开发社区中的使用频率较低,因为它在闭包与标准函数中的工作方式存在混淆。 大多数(如果不是全部)钩子在实践中都填充了常量。 将此作为单独的var
引用会使未来的开发人员感到困惑,他们可能会认为这是一个错误并更改它以适应模式,从而破坏您的代码。
如果您确实想动态引用loadMoreClicked
的状态,并且您不一定需要重新渲染组件,我实际上建议使用useRef()
而不是useState()
。
useRef
一个具有单个属性current
的对象,该属性保存您放入其中的任何值。 当您更改current
,您正在更新可变对象上的值。 因此,即使对对象的引用被及时冻结,它也引用了具有最新值的可用对象。
这看起来像:
function component() {
const loadMoreClicked = useRef(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked.current); // This references the property as it is currently defined
})
.catch(err => {
console.log(err);
});
}
const loadMore = () => {
loadMoreClicked.current = true; // property is uodated immediately
setTimeout(getBooks(), 5000);
};
}
这是有效的,因为虽然loadMoreClicked
在顶部定义为const
,但它是对对象的常量引用,而不是常量值。 被引用的对象可以随意改变。
这是 Javascript 中比较令人困惑的事情之一,它通常在教程中被掩盖,所以除非你有一些使用指针的后端经验,比如在 C 或 C++ 中,否则它会很奇怪。
因此,对于您正在做的事情,我建议使用 useRef() 而不是 useState()。 如果你真的想重新渲染组件,比如说,如果你想在加载内容时禁用一个按钮,然后在加载内容时重新启用它,我可能会同时使用两者,并重命名它们以更清楚它们的目的:
function component() {
const isLoadPending = useRef(false);
const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
if (isLoadPending.current) {
isLoadPending.current = false:
setLoadButtonDisabled(false);
}
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
isLoadPending.current = true;
setLoadButtonDisabled(true);
setTimeout(getBooks(), 5000);
};
}
它有点冗长,但它有效,并且可以将您的关注点分开。 ref 是你的标志,告诉你的组件它现在在做什么。 状态指示组件应如何呈现以反映按钮。
设置状态是一个即发即忘的操作。 在您的组件的整个函数执行完毕之前,您实际上不会看到它的变化。 请记住,在您可以使用 setter 函数之前,您将获得您的价值。 因此,当您设置状态时,您不会在此循环中更改任何内容,而是告诉 React 运行另一个循环。 它足够聪明,在第二个周期完成之前不渲染任何东西,所以它很快,但它仍然运行两个完整的周期,从上到下。
您可以使用useEffect
方法来监视loadMoreClicked
更新,例如componentDidUpdate
生命周期方法,并在其中调用setTimeout
,
useEffect(() => {
if(loadMoreClicked){
setTimeout(() => {
getBooks();
}, 5000);
}
}, [loadMoreClicked])
只有在loadMoreClicked
更改为true
我们才会调用setTimeout
。
这归结为闭包在 JavaScript 中的工作方式。 提供给setTimeout
的函数将从初始渲染中获取loadMoreClicked
变量,因为loadMoreClicked
没有发生变异。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.