在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离我更远了,快乐离我更近了