javascript - 即使延迟了(5秒)后,函数内部的React状态也不会改变

标签 javascript reactjs react-functional-component use-state

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

getBooks从端点获取数据。但是,当我在按钮上调用loadMore函数时,在getBooks函数内部(loadMoreClicked)未被更改,即使在延迟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>
  );
}

最佳答案

有两件事发生:


getBooks()使用周围函数中定义的const值。当函数在其定义之外引用constlet变量时,它将创建所谓的闭包。闭包从这些外部变量中获取值,并提供内部函数的值副本,就像创建函数时一样。在这种情况下,函数是在最初调用状态后立即构建的,并且loadMoreClicked设置为false
那么,为什么setLoadMore(true)没有触发重新渲染并重写函数呢?当我们设置状态时,重新渲染不会立即发生。它被添加到React管理的队列中。这意味着,当执行loadMore()时,setLoadMore(true)会说“在我运行完其余代码后更新状态”。重新渲染在函数结束后发生,因此使用的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在闭包与标准函数之间的工作方式混乱,因此开发社区较少使用它。实际上,大多数(如果不是全部)钩子都会填充const。将其作为单独的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,但它是对对象的常量引用,而不是常量值。您可以随意更改被引用的对象。

这是Java语言中比较混乱的事情之一,通常会在教程中加以掩饰,因此,除非您有一些使用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运行另一个周期。它足够聪明,不会在第二个周期完成之前渲染任何内容,因此它速度很快,但是仍然自上而下运行两个完整的周期。

关于javascript - 即使延迟了(5秒)后,函数内部的React状态也不会改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60680594/

相关文章:

javascript - 具有属性的 JSX 动态标签

javascript - D3-桑基错误 : Missing: (nodename)

javascript - React 子组件未从父组件发送更新状态

reactjs - 浏览器重新加载时的持久状态在 react 中不起作用

javascript - 为什么我的 Owl Carousel 项目是垂直排序而不是水平排序的?

javascript - JS - 不包括已给定值的随机数组项

javascript - jQuery 两列(一维)数组?

reactjs - Material UI CardHeader 组件文本样式中的问题

javascript - React 类内部与外部的功能组件

javascript - 如何确定事件的 WebGL 上下文的数量?