use*_*989 1 javascript reactjs react-functional-component use-state
在反应中,我使用功能组件,并且有两个函数(getBooks)和(loadMore)
getBooks从端点获取数据。但是,当我loadMore在函数内单击按钮调用getBooks函数(loadMoreClicked)时,即使在延迟(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>
);
}
Run Code Online (Sandbox Code Playgroud)
有两件事正在发生:
getBooks()使用const周围函数中定义的值。当函数引用const其let定义之外的变量时,它会创建所谓的闭包。闭包从这些外部变量中获取值,并为内部函数提供这些值的副本,就像构建函数时一样。在本例中,该函数是在最初调用状态后立即构建的,并loadMoreClicked设置为false。
那么为什么不setLoadMore(true)触发重新渲染并重写函数呢?当我们设置状态时,重新渲染不会立即发生。它被添加到 React 管理的队列中。这意味着,loadMore()执行时setLoadMore(true)会说“在运行完其余代码后更新状态”。重新渲染发生在函数结束之后,因此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
);
};
Run Code Online (Sandbox Code Playgroud)
还有第三种选择,即重写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);
};
}
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为,虽然loadMoreClickedconst在顶部被定义为 a ,它是对对象的常量引用,而不是常量值。被引用的对象可以根据需要进行更改。
这是 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);
};
}
Run Code Online (Sandbox Code Playgroud)
它有点冗长,但它有效,并且可以分离您的关注点。ref 是你的标志,告诉你的组件它现在正在做什么。状态指示组件应如何呈现以反映按钮。
设置状态是一种即发即忘的操作。在组件的整个函数执行完毕之前,您实际上不会看到其中的变化。请记住,您必须先获得值,然后才能使用 setter 函数。因此,当您设置状态时,您不会在此周期中更改任何内容,而是告诉 React 运行另一个周期。它足够聪明,不会在第二个周期完成之前渲染任何内容,因此速度很快,但它仍然从上到下运行两个完整的周期。
| 归档时间: |
|
| 查看次数: |
3247 次 |
| 最近记录: |