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

IT技术 javascript reactjs react-hooks
2021-04-28 18:16:55

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

我想这与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个回答

所以你的匿名函数被锁定在 的初始值上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 此外,现在您还没有设置scrollingfalse,因此您可以摆脱 condition 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}
    </>
  );