在React使用中避免竞争条件和内存泄漏

关注公众号 前端人,回复“加群”
添加无广告优质学习群
文章来源:Dev
文章地址:dev.to/saranshk/avoiding-race-conditions-and-memory-leaks-in-react-useeffect-3mme
让我们学习如何处理“Can’t perform a React state update on an unmounted component”警告
让我们看一下从API请求获取数据的实现,并查看此组件中是否有发生竞争情况的可能性:
import React, { useEffect} from 'react';
export default function UseEffectWithRaceCondition() {
  const [todo, setTodo] = useState(null);
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const newData = await response.json();
      setTodo(newData);
    };
    fetchData();
  }, []);
  if (data) {
    return <div>{data.title}div>;
  } else {
    return null;
  }
}
我们已经指定了一个空数组作为对useEffect React hook的依赖。因此,我们确保获取请求仅发生一次。但是此组件仍然容易出现争用情况和内存泄漏。如何?
如果API服务器花了一些时间来响应并且在接收到响应之前已卸载组件,则会发生内存泄漏。尽管已卸载该组件,但仍会在完成时收到对请求的响应。然后将解析响应并调用setTodo。React会发出警告:
无法在已卸载的组件上执行React状态更新。这是空操作,但它表明应用程序中发生内存泄漏。要修复,请取消使用useEffect清理功能中的所有订阅和异步任务。
消息非常简单。
相同问题的另一个潜在情况可能是待办事项列表ID作为道具被传递。
import React, { useEffect} from 'react';
export default function UseEffectWithRaceCondition( {id} ) {
  const [todo, setTodo] = useState(null);
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
      const newData = await response.json();
      setTodo(newData);
    };
    fetchData();
  }, [id]);
  if (data) {
    return <div>{data.title}div>;
  } else {
    return null;
  }
}
如果钩子在请求完成之前收到了另一个ID,而第二个请求在第一个请求之前完成了,那么我们将在组件中看到第一个请求的数据。
竞态条件问题的潜在解决方案 有两种方法可以解决此问题。两种方法都利用useEffect提供的清除功能。
我们可以使用布尔标志来确保组件已安装。这样,我们仅在标志为true时更新状态。而且,如果我们在一个组件内发出多个请求,我们将始终显示最后一个的数据。
每当卸载组件时,我们都可以使用AbortController取消先前的请求。IE中不支持AbortController。因此,如果要使用这种方法,我们需要考虑一下。
useEffect清理与布尔标志
useEffect(() => {
  let isComponentMounted = true;
    const fetchData = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const newData = await response.json();
      if(isComponentMounted) {
        setTodo(newData);
      }
    };
    fetchData();
    return () => {
      isComponentMounted = false;
    }
  }, []);
此修补程序依赖useEffect的清除功能的工作方式。如果一个组件渲染多次,则在执行下一个效果之前,将清除上一个效果。
由于这种工作方式,由于ID发生了更改,因此对于我们的其他多个请求示例也可以正常工作。从某种意义上说,我们仍然处于竞争状态,即在后台会有多个请求在运行。但是,只有上一个请求的结果才会显示在UI上。
使用AbortController进行useEffect清理
尽管以前的方法可行,但这并不是处理比赛条件的最佳方法。这些请求在后台进行。在后台使用过时的请求是不必要地消耗了用户的带宽。浏览器也限制了并发请求的最大数量(最大6-8)。
从上一篇有关如何取消HTTP提取请求的文章中,我们了解到已添加到DOM标准的AbortController API。我们可以利用它完全中止我们的请求。
useEffect(() => {
  let abortController = new AbortController();
    const fetchData = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', {
            signal: abortController.signal,
          });
      const newData = await response.json();
        setTodo(newData);
      }
      catch(error) {
         if (error.name === 'AbortError') {
          // Handling error thrown by aborting request
        }
      }
    };
    fetchData();
    return () => {
      abortController.abort();
    }
  }, []);
由于中止请求会引发错误,因此我们需要显式处理它。
而且该解决方案的工作方式与上一个类似。在重新渲染的情况下,执行下一个效果之前将执行清除功能。不同之处在于,由于我们使用的是AbortController,浏览器也会取消请求。
这是我们在使用React的useEffect钩子发出API请求时避免竞争条件的两种方法。如果要使用允许取消请求的某些第三方库作为一项功能,则可以使用Axios或React查询来提供许多其他功能。
回复 资料包领取我整理的进阶资料包回复 加群,加入前端进阶群console.log("文章点赞===文章点在看===你我都快乐"Bug离我更远了,快乐离我更近了 
