在反应中,我使用功能组件,我有两个功能(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>
);
}
最佳答案
有两件事发生: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
);
};
第三种选择是将
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
在顶部被定义为const
,但它是对对象的常量引用,而不是常量值。您可以随意更改被引用的对象。这是Java语言中比较混乱的事情之一,通常会在教程中加以掩饰,因此,除非您有一些使用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运行另一个周期。它足够聪明,不会在第二个周期完成之前渲染任何内容,因此它速度很快,但是仍然自上而下运行两个完整的周期。
关于javascript - 即使延迟了(5秒)后,函数内部的React状态也不会改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60680594/