【Bug解决】Can‘t perform a React state update on an unmounted component. This is > a no-op, but it...

发布时间 2023-11-02 10:46:56作者: 奔跑的太阳花
React 应用程序中我们遇到以下警告消息:

Can’t perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in a useEffect cleanup
function.

这是因为我们在使用异步调用时,造成了内存泄漏。

为什么异步调用可能会内存泄漏?

如果在卸载组件后更新状态,执行状态更新和运行异步操作的 React 组件就会导致内存泄漏问题,比如:

  1. 用户执行触发事件处理程序以从 API 获取数据的操作。
  2. 然后,用户点击一个链接,在完成第 1 步之前导航到另一个页面。
  3. 此时,第1步操作完成并传递从 API 获取到的数据并调用setState。

由于组件已卸载,并且函数正在已经卸载的组件中调用,因此会导致内存泄漏问题——并且在控制台中,会出现上述警告。

错误代码示例:

const [value, setValue] = useState('checking value...');
useEffect(() => {
	fetchValue().then(() => {
     setValue("done!"); // ⚠️ what if the component is no longer mounted ?
     // we got console warning of memory leak
   });
}, []);
如何避免此类问题

1、 使用变量标记组件状态

const [value, setValue] = useState('checking value...');
useEffect(() => {
let isMounted = true;
fetchValue().then(() => {
      if(isMounted ){
      setValue("done!"); // no more error
      } 
    });
   return () => {
    isMounted = false;
    };
}, []);

在上面的代码中,创建了一个变量isMounted,其初始值为true。当isMountedtrue时,更新状态并返回函数。否则,如果操作在完成之前被卸载,则函数isMounted以 false返回。

2、使用 AbortController

useEffect(() => {  
    let abortController = new AbortController();  
    // your async action is here  
    return () => {  
    abortController.abort();  
    }  
    }, []);
 
  • 在上述代码中,AbortController用来实现取消订阅效果。异步操作完成后,中止控制器取消订阅。

3、使用use-state-if-mounted Hook

const [value, setValue] = useStateIfMounted('checking value...');
    useEffect(() => {
    	fetchValue().then(() => {
          setValue("done!"); // no more error
        });
    }, []);
 

在上述代码中,使用了一个自定义的钩子 useStateIfMounted,它的作用用来在更新状态之前检查组件是否已安装!