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

共 3352字,需浏览 7分钟

 ·

2021-02-24 20:15

关注公众号 前端人,回复“加群

添加无广告优质学习群

文章来源: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离我更远了,快乐离我更近了

浏览 181
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报