React useState在窗口事件中不更新

时间:2020-03-05 08:26:29

标签: javascript reactjs react-hooks

状态确实在滚动条上设置,但是从事件侦听器记录下来,它似乎停留在初始值。

我猜这与在定义副作用时设置scrolling有关,但是我如何才能从滚动中触发状态更改?我假定的任何窗口事件都一样。

下面是一个代码框示例: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}
    </>
  );

2 个答案:

答案 0 :(得分:1)

事件侦听器回调仅初始化一次

这意味着那一刻该变量在那一刻也被“捕获”,因为在重新渲染时,您不会重新初始化事件侦听器。

有点像登机时刻的快照。

如果将console.log移到外面,您将看到它在重新渲染时发生变化,并再次设置滚动值。

  const [scrolling, setScrolling] = useState(false);

  useEffect(() => {
    window.addEventListener("scroll", () => {
      if (scrolling === false) setScrolling(true);
    });
  }, []);

  console.log(scrolling);

  return (
    <>
      scrolling: {scrolling}
    </>
  );

答案 1 :(得分:0)

因此,您的匿名函数被锁定在scrolling的初始值上。这就是闭包在JS中的工作方式,您最好找到一些漂亮的文章,这可能会有些棘手,并且大量依赖于闭包。

到目前为止,这里有3种不同的解决方案:

1。每次更改时重新创建并重新注册处理程序

useEffect(() => {
    const scrollHandler = () => {
      if (scrolling === false) setScrolling(true);
    };
    window.addEventListener("scroll", scrollHandler);
    return () => window.removeEventListener("scroll", scrollHandler);
  }, [scrolling]);

在遵循此路径的同时,请确保您是useEffect的{​​{3}}函数。这是一种很好的默认方法,但滚动它可能会影响性能,因为滚动事件经常触发

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),但是可以肯定的是,在实际情况下,您可能还会遇到某些问题一样。