状态确实在滚动中设置,但从eventlistener记录,它似乎停留在初始值。
我想这与定义副作用时设置scrolling
有关,但否则我怎么能从滚动中触发状态更改呢?我想,任何窗口活动也是如此。
下面是一个codesandbox示例: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中的工作方式,你最好找到一些关于这方面的漂亮文章,这可能会很棘手,钩子严重依赖闭包。
到目前为止,这里有3种不同的解决方案:
1.在每次更改时重新创建并重新注册处理程序
useEffect(() => {
const scrollHandler = () => {
if (scrolling === false) setScrolling(true);
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, [scrolling]);
在遵循此路径时,请确保从useEffect
返回清理函数。这是一种很好的默认方法,但对于滚动,可能会影响性能,因为滚动事件也经常触发。
2.通过引用访问数据
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不会触发重新渲染。因此,您需要有一些其他变量来更改它,从而触发重新渲染。
3.使用setter的功能版本访问最新值
(我认为这是首选方式(:
useEffect(() => {
const scrollHandler = () => {
setScrolling((currentScrolling) => {
if (!currentScrolling) return true;
return false;
});
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, []);
注意,即使是一次性使用效果,你最好还是返回清理函数。
PS同样,到目前为止,您还没有将scrolling
设置为false
,所以您可以摆脱条件if(scrolling === false)
,但在现实世界中,您肯定也会遇到类似的情况。
事件侦听器回调只初始化一次
这意味着此时的变量也会被"捕获",因为在重新发送时,您不会重新初始化事件侦听器。
这有点像是装载时刻的快照。
如果您将console.log移到外部,您会看到它随着重新发送的发生而发生变化,并再次设置滚动值。
const [scrolling, setScrolling] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
if (scrolling === false) setScrolling(true);
});
}, []);
console.log(scrolling);
return (
<>
scrolling: {scrolling}
</>
);
当我需要访问eventListener
中的state
(getState和setState(,而不必创建对该状态(或其所有状态(的引用时,一个对我个人很有帮助的解决方案是使用以下自定义挂钩:
export function useEventListener(eventName, functionToCall, element) {
const savedFunction = useRef();
useEffect(() => {
savedFunction.current = functionToCall;
}, [functionToCall]);
useEffect(() => {
if (!element) return;
const eventListener = (event) => savedFunction.current(event);
element.addEventListener(eventName, eventListener);
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
}
我所做的是引用eventListener
中要调用的函数。在我需要eventLister
的组件中,它看起来像这样:
useEventListener("mousemove", getAndSetState, myRef.current); //myRef.current can be directly the window object
function getAndSetState() {
setState(state + 1);
}
我留下了一个带有更完整代码的代码沙盒