问题描述
状态确实在滚动上设置,但从事件侦听器记录,它似乎停留在初始值.
State does get set on the scroll, but logged from the eventlistener, it seems to be stuck at the initial value.
我想这与定义副作用时设置的 scrolling
有关系,但是我怎么能从滚动中触发状态更改呢?我假设的任何窗口事件也是如此.
I guess it's something to do with scrolling
being set when the side effect's defined, but how could I trigger a state change from a scroll otherwise? Same goes for any window event I presume.
这是一个代码和框示例:https://codesandbox.io/s/react-test-zft3e
Here's a codesandbox example: https://codesandbox.io/s/react-test-zft3e
const [scrolling, setScrolling] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
console.log(scrolling);
if (scrolling === false) setScrolling(true);
});
}, []);
return (
<>
scrolling: {scrolling}
</>
);
推荐答案
所以你的匿名函数被锁定在 scrolling
的初始值上.这就是闭包在 JS 中的工作原理,你最好找一些关于它的漂亮文章,这可能会很棘手,而且钩子严重依赖闭包.
So your anonymous function is locked on initial value of scrolling
. It's how closures works in JS and you better find out some pretty article on that, it may be tricky some time and hooks heavily rely on closures.
到目前为止,这里有 3 种不同的解决方案:
So far there are 3 different solutions here:
useEffect(() => {
const scrollHandler = () => {
if (scrolling === false) setScrolling(true);
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, [scrolling]);
在遵循此路径时,请确保您的 返回清理 函数来自 useEffect
.这是很好的默认方法,但滚动它可能影响性能,因为滚动事件太触发.
while following this path ensure your are returning cleanup function from useEffect
. It's good default approach but for scrolling it may affect performance because scroll event triggers too often.
const scrolling = useRef(false);
useEffect(() => {
const handler = () => {
if (scrolling.current === false) scrolling.current = true;
};
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, []);
return (
<>
scrolling: {scrolling}
</>
);
缺点:更改 ref 不会触发重新渲染.所以你需要有一些其他的变量来改变它触发重新渲染.
downside: changing ref does not trigger re-render. So you need to have some other variable to change it triggering re-render.
(我认为这是这里的首选方式):
(I see it as preferred way here):
useEffect(() => {
const scrollHandler = () => {
setScrolling((currentScrolling) => {
if (!currentScrolling) return true;
return false;
});
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, []);
注意顺便说一句,即使是一次性使用效果,您也最好返回清理功能无论如何.
Note Btw even for one-time use effect you better return cleanup function anyway.
PS 此外,现在您还没有将 scrolling
设置为 false
,因此您可以摆脱条件 if(scrolling === false)
,但可以肯定的是,在现实世界中,您也可能会遇到类似的情况.
PS Also by now you don't set scrolling
to false
, so you could just get rid of condition if(scrolling === false)
, but sure in real world scenario you may also run into something alike.
这篇关于React useState 不会在窗口事件中更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!