使用 `useEffect` 根据 React 状态切换递归获取请求

Posted

技术标签:

【中文标题】使用 `useEffect` 根据 React 状态切换递归获取请求【英文标题】:Toggling a recursive fetch request based on React state using `useEffect` 【发布时间】:2021-12-04 10:16:24 【问题描述】:

我正在尝试使用 React 中的按钮启动和停止递归获取请求。我还想在每个请求之间有一个延迟。为此,我决定使用useEffect 挂钩并将其链接到由按钮控制的布尔状态。每当此状态更新时,useEffect 中的脚本就会检查它是真还是假。如果是真的,我想获取一些必要的数据并启动递归获取函数。如果为false,则表示递归函数已经在运行,因此应该取消正在进行的请求,停止递归,并清除用于实现请求之间延迟的setTimeout

下面的代码是我如何尝试解决这个问题的模型。但是,它不起作用,可能是由于以下原因:

变量requesttimeout 在每次重新渲染时重新创建。我认为这可以通过useRef 钩子解决;但是,我是 React 的初学者,还没有设法让它发挥作用。 我的函数似乎没有得到任何状态更新;例如,在recursiveFetch 中,当data2 应包含useEffect 中的提取请求设置的新值时,data2 始终为default

我尝试了许多不同的组合和方法来使其发挥作用,但没有发现任何成功。我应该如何解决这个问题?

import  useState, useEffect  from 'react';

const Component = () => 
    const [executing, setExecuting] = useState(false);

    const [data1, setData1] = useState('default');
    const [data2, setData2] = useState('default');

    const [data3, setData3] = useState('default');

    // I think these variables should be defined with `useRef` but I'm not sure...
    let request;
    let timeout;

    const recursiveFetch = () => 
        request = fetch(`https://website.com/$data2`,  method: 'POST' )
        .then((res) => 
            timeout = setTimeout(() => 
                recursiveFetch();
            , 1000);
        
    

    useEffect(() => 
        if(executing)
            // Fetch some necessary data
            fetch(`https://example.com/$data1`)
                .then((res) => res.json())
                .then((data) => 
                    setData2(data.exampleProperty);
                
            // Call recursive function
            recursiveFetch();
         else 
            // Cancel ongoing fetch request and clear timeout
            request.cancel();
            clearTimeout(timeout);
        
    , [executing])
    return (
        <>
            <Button onClick=() => setExecuting((prev) => !prev)></Button>
        </>
    );
;

export default Component;

【问题讨论】:

为什么需要递归地执行fetch?你调用什么 API 需要这个?这感觉像是一个可能的xy problem。 @ggorlen 我很确定这不是 XY 问题,因为递归地执行 fetch 正是我想要做的。为什么?主要是为了实验和学习 React。 @ggorlen 好吧,正如我所说,这是一种实验。另外,我不是在获取数据,而是在重复发出POST 请求。很久以前我已经设法让它在 vanilla javascript 中工作,但我应该在 React 中尝试一下,主要是为了更深入地了解钩子的使用。希望你能理解。 【参考方案1】:

首先,recursiveFetch 函数不是递归的,因为它实际上并没有在自己的堆栈帧中调用自己。该调用由一个异步函数(对setTimeout 的回调)进行,该函数在堆栈被清除时运行。有关异步函数何时运行的详细信息,请参阅What the heck is the event loop anyway?。

其次,这里不需要重复设置和清除setTimeouts。这就是setInterval 的用途。因为当executing 发生变化时你会重新渲染,所以你可以使用它来有条件地切换间隔。

第三,不要忘记从您的useEffect 返回一个清理函数,该函数会在组件卸载时清除您的超时。

感谢您出于教学目的进行实验,但值得一提的是,您正在探索的模式不是您在 React 中看到的那种事情,除非我误解了您的尝试达到。如果没有太多的动力,最好提供一个直接的解决方案,该解决方案似乎基本上可以以一种正常的、不花哨的方式完成您想要做的事情:

const Example = () => 
  const [executing, setExecuting] = React.useState(false);
  
  React.useEffect(() => 
    if (executing) 
      const interval = setInterval(() => 
        console.log("fetched");
      , 1000);
      return () => clearInterval(interval);
    
  , [executing]);
  
  return (
    <div>
      <p>check the browser console to see logs</p>
      <button onClick=() => setExecuting(prev => !prev)>
        executing ? "on" : "off"
      </button>
    </div>
  );
;

ReactDOM.render(<Example />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

如果您需要在开始间隔时获取一些数据,您可以尝试以下操作:

const Example = () => 
  const [executing, setExecuting] = React.useState(false);
  const [data, setData] = React.useState("default");
  
  React.useEffect(() => 
    if (!executing) 
      return;
    
    
    fetch("https://httpbin.org/get")
      .then(res => res.text())
      .then(data => setData(data))
      .catch(err => console.error(err))
    ;
    const interval = setInterval(() => 
      console.log("fetched");
    , 1000);
    return () => clearInterval(interval);
  , [executing]);
  
  return (
    <div>
      <p>check the browser console to see logs</p>
      <button onClick=() => setExecuting(prev => !prev)>
        executing ? "on" : "off"
      </button>
    </div>
  );
;

ReactDOM.render(<Example />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

如果在设置 data 之前间隔不应该开始(这似乎是您的意图,因为在 URL 中使用了 data2),您可以将其移动到最终的 then 回调中fetch 链。将interval 保留在外部范围内,以便可以将其清除,并确保在fetch 响应到达之前卸载组件时不要开始间隔。

如果这些与您的用例不完全匹配并且无法调整以满足您的需求,我建议提供更多详细信息以更好地激发您为什么需要做更复杂的事情。

【讨论】:

以上是关于使用 `useEffect` 根据 React 状态切换递归获取请求的主要内容,如果未能解决你的问题,请参考以下文章

react 钩子中的 useEffect 执行顺序及其内部清理逻辑是啥?

为啥从 React 的 useEffect 依赖列表中省略函数是不安全的?

从 React 中的 useEffect 挂钩更改复选框选中状态?

如何将firebase与react功能组件同步?

react--useEffect使用

useEffect中防抖为什么不起作用?react hooks中如何写防抖?