useEffect 中的 setTimeout 函数输出一个缓存的状态值

setTimeout function in useEffect outputs a cached state value

很简单。 我正在使用 Redux 来管理我的状态

我在 useEffect 函数中有一个 setTimeout 函数。

setTimeout 的超时值为 50000 毫秒。

我希望 SetTimeout 处理程序做什么

50000ms 之后,setTimeout 函数检查是否已收到 api 呼叫响应。

如果尚未收到响应,setTimeout 函数应重新发起 api 调用,因为那样调用将被视为超时。

回调处理程序在做什么

50000ms 之后,即使已收到响应,setTimeout 处理程序仍然会重新启动 api 调用。

我尝试记录状态的输出,然后它返回了缓存状态,即使状态已传递到 useEffect 函数的依赖数组并且应该已更新

调用api后,testDetails.isUpdatingTestDetails状态设置为false

我尝试了几种逻辑,其中 none 有效

逻辑 1

 useEffect(() => {
         //Notice how i check if the testDetails is being updated before initiating the setTimeout callback
        if (testDetails.isUpdatingTestDetails === true) {
         
            setTimeout(() => {
// Inside the settimeout function the same check is also done.
// even though before 50 seconds the response is being received , the function logs the text simulating the reinitiation of an api call
                return testDetails.isUpdatingTestDetails === true &&
                    console.log("After 50 Seconds You Need To Refetch This Data")
            }, 50000);
        }
 

    }, [testDetails.isUpdatingTestDetails, testDetails])

逻辑 2

     useEffect(() => {
         setTimeout(() => {
           return testDetails.isUpdatingTestDetails === true &&
             console.log("After 50 Seconds You Need To Refetch This Data")
            }, 50000);
    }, [testDetails.isUpdatingTestDetails, testDetails])

None 我在上面应用的逻辑正在运行。

状态过时的原因:

useEffect的回调形成了对当时状态的闭包。因此,当执行超时的回调时,它只能使用旧状态,即使状态同时更新也是如此。

一旦状态改变,useEffect 将再次运行(因为状态是依赖项)并开始新的超时。

第二次超时将使用新状态,因为闭包是用新状态形成的。如果状态第三次更改,此超时也容易受到陈旧状态问题的影响。


解法:

当状态改变时,您可以清除之前的超时。这样,除非是最新的,否则不会执行超时的回调。

export default function App() {
  const [state, setState] = useState(true);

  useEffect(() => {
    const timeout = setTimeout(() => {
      console.log(state);
    }, 5000);

    return () => {
      // clears timeout before running the new effect
      clearTimeout(timeout);
    };
  }, [state]);

  return (
    <div className="App">
      <h1>State: {state.toString()}</h1>
      <button onClick={() => setState(false)}>update</button>
    </div>
  );
}

const { useState, useEffect } = React;

function App() {
  const [state, setState] = useState(true);

  useEffect(() => {
    const timeout = setTimeout(() => {
      console.log(state);
    }, 5000);

    return () => {
      // clears timeout before running the new effect
      clearTimeout(timeout);
    };
  }, [state]);

  return (
    <div className="App">
      <h1>State: {state.toString()}</h1>
      <button onClick={() => setState(false)}>update</button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>


如果你想运行超时而不增加延迟的替代解决方案。如果延迟后状态发生变化,则不会启动新的超时。

您可以使用 useRef 挂钩随时引用最新状态。

这是一个例子。您可以修改以下内容以使用您的变量和逻辑。

export default function App() {
  const [state, setState] = useState(true);
  const stateRef = useRef(state);

  // this effect doesn't need any dependencies
  useEffect(() => {
    const timeout = setTimeout(() => {
      // use `stateRef.current` to read the latest state instead of `state`
      console.log(stateRef.current);
    }, 5000);

    return () => {
      // just to clear the timeout when component unmounts
      clearTimeout(timeout);
    };
  }, []);

  // this effect updates the ref when state changes
  useEffect(() => {
    stateRef.current = state;
  }, [state]);

  return (
    <div className="App">
      <h1>State: {state.toString()}</h1>
      <button onClick={() => setState(false)}>update</button>
    </div>
  );
}

const { useState, useEffect, useRef } = React;

function App() {
  const [state, setState] = useState(true);
  const stateRef = useRef(state);

  // this effect doesn't need any dependencies
  useEffect(() => {
    const timeout = setTimeout(() => {
      // use `stateRef.current` to read the latest state instead of `state`
      console.log(stateRef.current);
    }, 5000);

    return () => {
      // just to clear the timeout when component unmounts
      clearTimeout(timeout);
    };
  }, []);

  // this effect updates the ref when state changes
  useEffect(() => {
    stateRef.current = state;
  }, [state]);

  return (
    <div className="App">
      <h1>State: {state.toString()}</h1>
      <button onClick={() => setState(false)}>update</button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>