React useState 不会在窗口事件中更新



状态确实在滚动中设置,但从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);
}

我留下了一个带有更完整代码的代码沙盒

相关内容

  • 没有找到相关文章

最新更新