React在未挂载组件后更新状态的错误
2023 reactReact 组件在卸载后,如果组件内有正在处理的异步函数。那么在函数处理后,需要更新组件的状态,会得到一个警告。
出现的原因
最可能出现在以下几个情况:
- 在组件内调用了网络请求,网络请求未响应就卸载了组件
- 在组件内调用了定时器,定时器还没执行就卸载了组件
网络请求
最常见的情况就是在网络请求中,当我们发起的网络请求还未返回,我们切换了组件,导致之前的组件被卸载掉。
// Posts.tsx
import { useEffect, useState } from 'react';
type Post = { id: number | string; title: string };
const Posts = () => {
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
setPosts(data);
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
return (
<ul>
{posts.map((post) => {
return <li key={post.id}>{post.title}</li>;
})}
</ul>
);
};
export default Posts;
// App.tsx
import { useState } from 'react';
import Posts from './Posts';
export default function App() {
const [showPost, setShowPost] = useState(false);
return (
<div>
<button onClick={() => setShowPost(true)}>Show Post</button>
<button onClick={() => setShowPost(false)}>Hide Post</button>
{showPost && <Posts />}
</div>
);
}
https://codesandbox.io/s/fetch-call-1xt2tb
在上面的 Posts 组件挂载后,会发起向 JSON Placeholder 的 API 请求,并在请求成功后展示博客列表。
当我们点击 ‘Fetch Posts’ 按钮后,并且迅速点击 ‘Hide Posts’(也可以通过开发者工具,将 ‘Network’ 调到慢速,防止网速过快)。在网络请求成功返回之前,我们切换组件的状态,就会得到相应的错误。
定时器
另一种很常见的情况就是 setTimeout 和 setInterval 定时器的使用。
// Timeout.tsx
import { useState } from 'react';
const Timeout = () => {
const [counter, setCounter] = useState(0);
setTimeout(() => setCounter(counter + 1), 5000);
return (
<>
<div>Counter: {counter}</div>
<div>The Timeout will add after 5 seconds.</div>
</>
);
};
export default Timeout;
https://codesandbox.io/s/settimeout-0c5ro1
当 Timeout 组件挂载后,会设置 5s 后更新 counter 变量,期间我们卸载掉组件,就会得到相应的错误。
解决方案
以上就是常见的两种遇到该警告提示的情况。解决方案有以下方案可以参考。
更新前检测组件状态
使用 useRef 关联组件的生命周期,当组件挂载时设置变量为 true,当卸载之后设置变量为 false。在异步函数更新状态的时候,先判断 isMounted.current
的变量值。
const isMounted = useRef();
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
if (isMounted.current) {
setState(..);
}
https://codesandbox.io/s/settimeout-useref-zjnr7c
可以将上面的 useRef 代码封装成自定义的 hook,也可以直接使用第三方封装的,如 react-use 的 useAsync
import { useAsync } from 'react-use';
type Post = { id: number | string; title: string };
const Posts = () => {
const state = useAsync(async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
return data;
} catch (e) {
console.log(e);
}
}, []);
return (
<ul>
{state.value &&
state.value.map((post: Post) => {
return <li key={post.id}>{post.title}</li>;
})}
</ul>
);
};
export default Posts;
https://codesandbox.io/s/fetch-call-useasync-evtz0n
卸载时处理异步回调
可以在卸载组件时,处理掉异步调用函数的组件。
对于接口的调用,我们可以通过调用 AbortController
的接口,取消网络请求。
// Posts.tsx
import { useEffect, useState } from 'react';
type Post = { id: number | string; title: string };
const Posts = () => {
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
signal: signal,
});
const data = await response.json();
setPosts(data);
} catch (e) {
console.log(e);
}
};
fetchData();
return () => {
controller.abort();
};
}, []);
return (
<ul>
{posts.map((post) => {
return <li key={post.id}>{post.title}</li>;
})}
</ul>
);
};
export default Posts;
https://codesandbox.io/s/fetch-call-abortcontroller-x0c5ro
对于 fetch, axios 均可以使用 AbortController 来进行管理卸载,当请求时,附带参数 AbortSignal
,卸载组件时,调用 abort()
方法。
对于定时器的取消,则是调用 clearTimeout
和 clearInterval
来取消定时器。
import { useEffect, useState } from 'react';
const Timeout = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timerID = setTimeout(() => setCounter((counter) => counter + 1), 5000);
return () => clearTimeout(timerID);
}, []);
return (
<>
<div>Counter: {counter}</div>
<div>The Counter will add after 5 seconds.</div>
</>
);
};
export default Timeout;
https://codesandbox.io/s/settimeout-cleartimeout-yhup3u
最后
以上就是常见的警告提示以及对应的解决方案。
但是,最后,在 React 18 版本中,取消了这个警告提示!!!
https://codesandbox.io/s/settimeout-react-18-w22v5u
具体取消原因可以查看 Update to remove the “setState on unmounted component” warning #82,大致意思大多数情况并不会真正造成内存泄漏。