有两件事发生:
getBooks()
正在使用const
在周围函数中定义的值。当一个函数在其定义之外引用const
或let
变量时,它会创建所谓的闭包。闭包从那些外部变量中获取值,并为内部函数提供这些值的副本,就像它们在构建函数时一样。在这种情况下,该函数是在最初调用状态后立即构建的,loadMoreClicked
设置为false
.
那么为什么不setLoadMore(true)
触发重新渲染并重写函数呢?当我们设置状态时,重新渲染不会立即发生。它被添加到 React 管理的队列中。这意味着,当loadMore()
执行时,setLoadMore(true)
表示“在我运行完其余代码后更新状态”。重新渲染发生在函数结束之后,因此getBooks()
used 的副本是在此循环中构建并排队的副本,其中包含内置的原始值。
对于您正在执行的操作,您可能希望在超时时调用不同的函数,具体取决于是否单击了按钮。或者,您可以根据是否要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
开发社区的使用频率较低,因为它在闭包和标准函数中的工作方式令人困惑。在实践中,大多数(如果不是全部)钩子都会填充 const。将其作为单独的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
定义为 a ,但它是对对象的常量引用,而不是常量值。被引用的对象可以随意改变。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 运行另一个循环。在第二个周期完成之前不渲染任何东西是足够聪明的,所以它很快,但它仍然运行两个完整的周期,从上到下。