【问题标题】:Cannot retrieve current state inside async function in React.js无法在 React.js 中的异步函数内检索当前状态
【发布时间】:2023-01-30 04:45:19
【问题描述】:

我在顶层组件(App)创建了一些状态,但是似乎当这个状态被更新时,更新的状态并没有被useEffect()中定义的异步函数读取(它仍然使用以前的值),更多细节以下:

我试图在 useEffect() 中定义的异步函数 toggleProcessing 中检索 const processing 的状态,以便当 processing 变为 false 时,异步函数从 while 循环中退出。但是,似乎当processing更新为false时,while循环仍然继续执行。

行为应该如下:按下“开始处理”按钮应该每两秒控制台记录一次“正在处理...”,当再次按下同一个按钮(现在标记为“停止处理”)时,“停止处理”应该被控制台记录。但是,在实践中,“停止处理”永远不会记录在控制台中,而“处理中”会永远持续记录。

下面是代码:

import React, { useState, useEffect} from 'react'

const App = () => {

    const [processing, setProcessing] = useState(false)

    const sleep = (ms) => {
        return new Promise(resolve => setTimeout(resolve, ms))
    }

    useEffect(() => {
        const toggleProcessing = async () => {
            while (processing) {
                console.log('Processing...')
                await sleep(2000);
            }
            console.log('Stopping Processing')
        }
        if (processing) {
            toggleProcessing()      // async function
        }
    }, [processing])

    return (
        <>
            <button onClick={() => setProcessing(current => !current)}>{processing ? 'Stop Processing' : 'Begin Processing'}</button>
        </>
    )
}

export default App;

它实际上归结为能够在异步函数中读取 processing 的更新状态,但尽管阅读了类似的帖子,但我还没有找到一种方法来做到这一点。

先感谢您!

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    如果您希望在使用超时时访问状态,最好保留对该变量的引用。您可以使用 useRef 挂钩实现此目的。只需添加一个带有处理值的 ref,并记得更新它。

    const [processing, setProcessing] = useState<boolean>(false);
    const processingRef = useRef(null);
    
    useEffect(() => {
        processingRef.current = processing;
    }, [processing]);
    

    【讨论】:

      【解决方案2】:

      这是工作代码:

      import React, { useState, useEffect, useRef} from 'react'
      
      const App = () => {
      
          const [processing, setProcessing] = useState(false)
          const processingRef = useRef(null);
      
          const sleep = (ms) => {
              return new Promise(resolve => setTimeout(resolve, ms))
          }
      
          useEffect(() => {
              const toggleProcessing = async () => {
                  while (processingRef.current) {
                      console.log('Processing')
                      await sleep(2000);
                  }
                  console.log('Stopping Processing')
              }
              processingRef.current = processing;
              if (processing) {
                  toggleProcessing()      // async function
              }
          }, [processing])
      
          return (
              <>
                  <button onClick={() => setProcessing(current => !current)}>{processing ? 'Stop Processing' : 'Begin Processing'}</button>
              </>
          )
      }
      
      export default App;
      

      【讨论】:

        【解决方案3】:

        我感兴趣的是它是如何工作的,以及你的最终解决方案是基于公认的答案。我根据 Dan Abramov 的 useInterval hook 拼凑了一个解决方案,并认为这个连同一些相关资源的链接可能对其他人有用。

        我很好奇,您决定使用 setTimeout 并引入 async/awaitwhile 循环而不是使用 setInterval 有什么具体原因吗?我想知道这意味着什么。你会处理 clearTimeout 清理所有这些计时器的效果吗?您的最终解决方案是什么样的?

        使用 useInterval 的演示/解决方案

        https://codesandbox.io/s/useinterval-example-processing-ik61ho

        import React, { useState, useEffect, useRef } from "react";
        
        const App = () => {
          const [processing, setProcessing] = useState(false);
        
          useInterval(() => console.log("processing"), 2000, processing);
        
          return (
            <div>
              <button onClick={() => setProcessing((prev) => !prev)}>
                {processing ? "Stop Processing" : "Begin Processing"}
              </button>
            </div>
          );
        };
        
        function useInterval(callback, delay, processing) {
          const callbackRef = useRef();
        
          // Remember the latest callback.
          useEffect(() => {
            callbackRef.current = callback;
          }, [callback]);
        
          // Set up the interval.
          useEffect(() => {
            function tick() {
              callbackRef.current();
            }
            if (delay !== null && processing) {
              let id = setInterval(tick, delay);
        
              console.log(`begin processing and timer with ID ${id} running...`);
        
              // Clear timer on clean up.
              return () => {
                console.log(`clearing timer with ID ${id}`);
                console.log("stopped");
                clearInterval(id);
              };
            }
          }, [delay, processing]);
        }
        
        export default App;
        

        相关链接

        Dan Abramov - Making setInterval Declarative with React Hooks

        SO Question: React hooks - right way to clear timeouts and intervals

        setTimeout and clearTimeout in React with Hooks(通过清除效果中的清理计时器来避免内存泄漏)

        【讨论】:

        • 老实说,我不知道setTimeout,因为我对 React 很陌生。请在我在此评论部分提供的答案中找到最终为我工作的代码。
        猜你喜欢
        • 2016-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        • 2020-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多