如何在 React 中正确地使用 useEffect 处理异步请求并避免内存泄漏?

如何在 React 中正确地使用 useEffect 处理异步请求并避免内存泄漏?

在 React 中,使用 useEffect 执行异步操作(如数据请求)时,若组件在请求完成前被卸载,直接更新状态会导致“Can't perform a React state update on an unmounted component”警告,这不仅是一种内存泄漏隐患,也可能引发 bug。

正确做法是使用“取消标记”(cleanup function + abort controller 或布尔标记)来确保只在组件挂载时才更新状态。

示例(使用布尔标记):

import { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true; // 标记组件是否仍挂载

    const fetchData = async () => {
      const response = await fetch('/api/data');
      const result = await response.json();
      if (isMounted) {
        setData(result); // 仅在组件未卸载时更新状态
      }
    };

    fetchData();

    return () => {
      isMounted = false; // 组件卸载时清理
    };
  }, []);

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

更现代的做法是使用 AbortController(适用于 fetch):

useEffect(() => {
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch('/api/data', {
        signal: controller.signal
      });
      const result = await response.json();
      setData(result);
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('Fetch error:', error);
      }
    }
  };

  fetchData();

  return () => {
    controller.abort(); // 取消正在进行的请求
  };
}, []);

解析:

  • useEffect 的返回函数会在组件卸载或依赖变化前执行,是清理副作用的理想位置。
  • 使用 isMounted 或 AbortController 可有效防止对已卸载组件的状态更新。
  • AbortController 是浏览器原生支持的取消机制,更符合标准,推荐用于 fetch 请求;而 isMounted 更通用,适用于非 fetch 的异步操作(如 setTimeout、第三方库等)。

注意:React 18 严格模式下,useEffect 可能会运行两次(开发环境),此时 cleanup 也会执行,因此确保 cleanup 逻辑健壮非常重要。

发表评论 (审核通过后显示评论):

昵称:
邮箱:
内容: