繁体   English   中英

即使在延迟(5 秒)调用后,函数内的 React 状态也不会改变

[英]React state inside a function is not changing even after calling it with a delay of (5 seconds)

在反应中,我正在使用功能组件,并且有两个函数 (getBooks) 和 (loadMore)

getBooksgetBooks获取数据。 但是,当我在getBooks函数 (loadMoreClicked) 内的按钮单击上调用loadMore函数时,即使在延迟(5 秒)调用它之后,它也不会更改它使用以前的状态。 但是当我再次调用loadMore时,状态会发生变化并且一切正常。

有人可以解释为什么最初调用 (getBooks) 时的 (loadMoreClicked) 甚至在延迟 5 秒后调用它也没有更新。

function component() {
  const [loadMoreClicked, setLoadMore] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`; //this is my end point
    axios
      .get(endPoint, {
        params: newFilters
      })
      .then(res => {
        console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
      })
      .catch(err => {
        console.log(err);
      });
  };

  const loadMore = () => {
    setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)

    setTimeout(() => {
      getBooks(); // i am calling (getBooks()) after 5 seconds.
    }, 5000);
  };

  return (
    <div>
      <button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
      function
    </div>
  );
}

有两件事正在发生:

  1. getBooks()是使用const在周围功能中定义的值。 当函数在其定义之外引用constlet变量时,它会创建所谓的闭包 闭包从这些外部变量中获取值,并在构建函数时为内部函数提供值的副本 在这种情况下,该函数是在最初调用状态后立即构建的,并将loadMoreClicked设置为false

  2. 那么为什么setLoadMore(true)触发重新渲染并重写函数呢? 当我们设置状态时,重新渲染不会立即发生。 它被添加到 React 管理的队列中。 这意味着,当执行loadMore()时, setLoadMore(true)表示“在我运行完其余代码后更新状态”。 重新渲染发生在函数结束之后,因此使用的getBooks()副本是在此循环中构建和排队的副本, getBooks()内置了原始值。

对于您正在执行的操作,您可能希望在超时时调用不同的函数,具体取决于按钮是否被单击。 或者,您可以创建另一个更直接的闭包,具体取决于您是否希望getBooks()考虑单击按钮,如下所示:

const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
  () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
    })
    .catch(err => {
      console.log(err);
    });
  }

...

const loadMore = () => {
  setLoadMore(true);
  setTimeout(
    getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
    5000
  );
};

还有第三个选项,即将const [loadMoreClicked, setLoadMore]重写为var [loadMoreClicked, setLoadMore] 虽然引用const变量会在那一刻冻结值,但var不会。 var允许函数动态引用变量,以便在函数执行时确定值,而不是在定义函数时确定。

这听起来像是一个快速而简单的解决方法,但在闭包中使用时可能会导致混淆,例如上面的第二个解决方案。 在这种情况下,由于闭包的工作方式,该值再次固定。 因此,您的代码会将值冻结在闭包中,但不会冻结在常规函数中,这可能会导致更多的混乱。

我个人的建议是保留const定义。 var在开发社区中的使用频率较低,因为它在闭包与标准函数中的工作方式存在混淆。 大多数(如果不是全部)钩子在实践中都填充了常量。 将此作为单独的var引用会使未来的开发人员感到困惑,他们可能会认为这是一个错误并更改它以适应模式,从而破坏您的代码。

如果您确实想动态引用loadMoreClicked的状态,并且您不一定需要重新渲染组件,我实际上建议使用useRef()而不是useState()

useRef一个具有单个属性current的对象,该属性保存您放入其中的任何值。 当您更改current ,您正在更新可变对象上的值。 因此,即使对对象的引用被及时冻结,它也引用了具有最新值的可用对象。

这看起来像:

function component() {
  const loadMoreClicked = useRef(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(loadMoreClicked.current); // This references the property as it is currently defined
    })
    .catch(err => {
      console.log(err);
    });
 }


  const loadMore = () => {
    loadMoreClicked.current = true; // property is uodated immediately
    setTimeout(getBooks(), 5000);
  };

}

这是有效的,因为虽然loadMoreClicked在顶部定义为const ,但它是对对象的常量引用,而不是常量值。 被引用的对象可以随意改变。

这是 Javascript 中比较令人困惑的事情之一,它通常在教程中被掩盖,所以除非你有一些使用指针的后端经验,比如在 C 或 C++ 中,否则它会很奇怪。

因此,对于您正在做的事情,我建议使用 useRef() 而不是 useState()。 如果你真的想重新渲染组件,比如说,如果你想在加载内容时禁用一个按钮,然后在加载内容时重新启用它,我可能会同时使用两者,并重命名它们以更清楚它们的目的:

function component() {
  const isLoadPending = useRef(false);
  const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      if (isLoadPending.current) {
        isLoadPending.current = false:
        setLoadButtonDisabled(false);
      }
    })
    .catch(err => {
      console.log(err);
    });
 };

  const loadMore = () => {
    isLoadPending.current = true;
    setLoadButtonDisabled(true);
    setTimeout(getBooks(), 5000);
  };

}

它有点冗长,但它有效,并且可以将您的关注点分开。 ref 是你的标志,告诉你的组件它现在在做什么。 状态指示组件应如何呈现以反映按钮。

设置状态是一个即发即忘的操作。 在您的组件的整个函数执行完毕之前,您实际上不会看到它的变化。 请记住,在您可以使用 setter 函数之前,您将获得您的价值。 因此,当您设置状态时,您不会在此循环中更改任何内容,而是告诉 React 运行另一个循环。 它足够聪明,在第二个周期完成之前不渲染任何东西,所以它很快,但它仍然运行两个完整的周期,从上到下。

您可以使用useEffect方法来监视loadMoreClicked更新,例如componentDidUpdate生命周期方法,并在其中调用setTimeout

useEffect(() => {
    if(loadMoreClicked){
      setTimeout(() => {
        getBooks();
      }, 5000);
    }
  }, [loadMoreClicked])

只有在loadMoreClicked更改为true我们才会调用setTimeout

这归结为闭包在 JavaScript 中的工作方式。 提供给setTimeout的函数将从初始渲染中获取loadMoreClicked变量,因为loadMoreClicked没有发生变异。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM