在滚动事件上侦听的函数中未更新反应挂钩

时间:2019-06-19 17:19:29

标签: javascript reactjs events react-hooks

我有一个功能handleScroll,它在滚动事件中被监听。此函数必须更新isFetching(以false开头,并且必须更改布尔值)。

功能handleScroll已正确监听,如console.log所示。但是,isFetching始终为假。 似乎从未读过setIsFetching。我认为,另一种选择就像eventListener冻结了handleScroll函数的第一个版本。

我该怎么做才能更新该函数中的钩子? 这是代码和codesandbox的简化版本:

/* <div id='root'></div> */
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
    if (immediate && !timeout) func.apply(context, args);
  };
};
const App = () => {
  const [isFetching, setIsFetching] = useState(false);
  const handleScroll = debounce(() => {
    setIsFetching(!isFetching);
    console.log({ isFetching });
  }, 300);
  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);
  return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);

更新

我将一个空数组作为第二个参数放在useEffect中,因为我希望第一个参数函数仅在componentDidMount()上触发一次

3 个答案:

答案 0 :(得分:1)

为了从useEffect回调内部监听状态变化(当您不进行任何道具更新时),可以将状态保存在组件范围之外的变量中,然后而不是直接使用它。

这里有代码:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const debounce = (func, wait, immediate) => {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
    if (immediate && !timeout) func.apply(context, args);
  };
};

let isFetchingState;

const App = () => {
  const [isFetching, setIsFetching] = useState(false);

  isFetchingState = isFetching;

  const handleScroll = debounce(() => {
    setIsFetching(!isFetchingState);
    console.log({ isFetchingState });
  }, 300);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return <div style={{ height: "1280px" }}>Hello world</div>;
};

const root = document.getElementById("root");

if (root) ReactDOM.render(<App />, root);

答案 1 :(得分:1)

isFetching添加为对useEffect的依赖项

虽然我无法提供深入的解释,但我可以说您基本上是在useEffect中对React撒谎,因为您说效果不提供任何依赖关系,因为它提供了空的依赖关系数组,传递所有包含在您的效果中的变量。

还要在每次组件re-render时创建一个新函数,以避免将此函数移到useEffect内或将其包装在useCallback内,这将不会重新创建该函数除非依赖项数组中的某些内容发生改变

useEffect(
  () => {
    const handleScroll = debounce(() => {
      setIsFetching(prevState => !prevState);
      console.log({ isFetching });
    }, 300);
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  },
  [isFetching]
);

或与useCallback

const handleScroll = useCallback(
  debounce(() => {
    setIsFetching(prevState => !prevState);
    console.log({ isFetching });
  }, 300),
  [isFetching]
);

useEffect(
  () => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  },
  [isFetching]
);

complete guide to useEffect

答案 2 :(得分:1)

您已经传递了一个空数组作为useEffect的第二个参数。这就是您在运行后不再被称为useEffect的关键问题。

要解决此问题,只需不要传递第二个参数。

 useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  });

或者,只要更改属性,就调用useEffect:

useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [isFetching]); // re-run useEffect on isFetching changed

这类似于我们在componentDidUpdate中所做的事情:

if (prevState.count !== this.state.count) {
  // do the stuff

有关更多详细信息,请参见documentation本身。

文档中的注释:

  

如果要运行效果并仅将其清理一次(在挂载和卸载时),则可以将空数组([])作为第二个参数传递。这告诉React,您的效果不依赖于道具或状态的任何值,因此它不需要重新运行。这不是特殊情况,它直接取决于依赖项数组始终如何工作。

     

如果传递一个空数组([]),则效果内的道具和状态将始终具有其初始值。尽管将[]作为第二个参数传递给了更熟悉的componentDidMount和componentWillUnmount心智模型,但通常会有更好的解决方案来避免过于频繁地重新运行效果。另外,别忘了React在浏览器绘制完成之后才推迟useEffect的运行,因此进行额外的工作不会有什么问题。