【问题标题】:React useState doesn't update in window eventsReact useState 不会在窗口事件中更新
【发布时间】:2020-03-05 08:26:29
【问题描述】:

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

我想这与定义副作用时设置 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}
    </>
  );

【问题讨论】:

  • 有什么问题?它按预期工作
  • 如果你开始滚动,你的状态就改变了
  • 尝试将你的函数移到外面:const handleScroll = useCallback(e =&gt; { console.log(`scrolling:${scrolling}`); if (scrolling === "no") setScrolling("yes!"); }, []);

标签: javascript reactjs react-hooks


【解决方案1】:

所以你的匿名函数被锁定在scrolling 的初始值上。这就是闭包在 JS 中的工作方式,您最好找到一些关于它的漂亮文章,这可能会有些棘手,并且钩子高度依赖于闭包。

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

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

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

在遵循此路径时,请确保您是来自 useEffectreturning cleanup 函数。这是一个很好的默认方法,但对于滚动它可能会影响性能,因为滚动事件触发频繁。

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),但可以肯定的是,在现实世界中您也可能会遇到类似的情况。

【讨论】:

  • 我认为最好的变体是第三个。因为第一个打破了钩子的规则。 reactjs.org/docs/hooks-rules.html
  • 虽然我也认为第三种方式最好,但我看不出首先违反钩子规则的方式。你能说一些细节吗?
  • Don’t call Hooks inside loops, conditions, or nested functions。在这种情况下,setScrolling 在条件下被调用
  • @KostyaTresko 不,你误会了。你不能调用if(...) [a] = useState(...),但是这条规则不适用于钩子返回的数据。所以setScrolling 可以在任何地方调用,甚至可以通过 props 传递给 child。
  • 哦,好的。谢谢解释
【解决方案2】:

事件监听回调只初始化一次

这意味着此时的变量也被“困住”了,因为在重新渲染时,您并没有重新初始化事件侦听器。

这有点像挂载时刻的快照。

如果你将 console.log 移到外面,你会看到它随着重新渲染的发生而改变,并再次设置滚动值。

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

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

  console.log(scrolling);

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-19
    • 2022-08-14
    • 1970-01-01
    • 2020-12-29
    • 2020-12-22
    • 2020-03-27
    • 2022-12-30
    • 1970-01-01
    相关资源
    最近更新 更多