You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React删除元素触发“无法在已卸载组件更新状态”警告的排查与解决

React卸载组件后状态更新警告问题分析与解决

问题描述

我有一个React页面,用Card组件展示数据列表。页面加载时通过useQuery从GraphQL(Apollo Client)获取数据,支持Card的编辑、删除、新建操作,并用本地state存储获取到的结果。执行删除操作的mutation后,列表能正常重新渲染,但会出现React警告:Can't perform a React state update on an unmounted component。

相关核心代码如下:

初始化与state部分

const {data: indicatorsData} = queryIndicators()
const {createQueryIndicator} = createQueryIndicatorMutation()
const {deleteQueryIndicator} = deleteQueryIndicatorMutation()

const [isOpened, setIsOpened] = useState(false)
const [form] = useForm()
const [displayedQueryIndicators, setDisplayedQueryIndicators] =
    useState<QueryIndicator[]>([])

useEffect(() => {
  if (indicatorsData) {
    setDisplayedQueryIndicators(() => indicatorsData.queryIndicators)
  }
}, [indicatorsData])

删除操作逻辑

const handleCancel = () => {
  setIsOpened(false)
}

const handleDeleteIndicator = (uuid: any) => {
  deleteQueryIndicator({variables: {uuid}})
  .then(result => {
    const newList = displayedQueryIndicators.filter(
        indicator => indicator.uuid !== result.data.deleteQueryIndicator)
    setDisplayedQueryIndicators(newList)
  })
  .catch((info) => {console.log(info)})
}

页面渲染部分(删除按钮)

<Row gutter={12}>
  <Col span={8}>
    <EditOutlined onClick={() => console.log("")} key="edit"/>
  </Col>
  <Col span={8}>
    <RedoOutlined onClick={() => console.log("")} key="refresh"/>
  </Col>
  <Col span={8}>
    <Popconfirm
        placement="top" title={'Are you sure ?'}
        onConfirm={() => handleDeleteIndicator(indicator.uuid)}
        okText="Yes"
        cancelText="No"
    >
      <DeleteOutlined onClick={() => console.log("")} key="delete"/>
    </Popconfirm>
  </Col>
</Row>

问题原因

这个警告的本质是:异步操作(这里是deleteQueryIndicator的mutation请求)完成后,回调函数里尝试更新一个已经被卸载的组件的state。

具体来说,当用户触发删除操作后,如果在mutation请求还没完成的情况下,组件就被卸载了(比如用户跳转到其他页面),此时then回调里的setDisplayedQueryIndicators依然会执行,试图更新已不存在的组件的状态,React就会抛出这个警告。

另外,当前手动维护displayedQueryIndicators本地state的写法,还可能带来竞态问题(比如多个异步操作顺序执行导致state更新错误)。

解决方案

方案1:使用Apollo Client缓存自动更新(推荐)

Apollo Client本身具备缓存管理能力,完全不需要手动维护本地state来同步列表数据。直接利用Apollo的缓存更新机制,让useQuery自动响应缓存变化重新渲染列表,从根源上避免手动setState带来的问题。

步骤:

  1. 移除本地displayedQueryIndicators状态和对应的useEffect同步逻辑,直接用useQuery返回的indicatorsData.queryIndicators渲染列表。
  2. 在执行deleteQueryIndicator时,通过update函数手动更新Apollo缓存:
// 假设queryIndicators对应的GraphQL文档变量为queryIndicatorsDocument
const handleDeleteIndicator = (uuid: any) => {
  deleteQueryIndicator({
    variables: { uuid },
    update(cache) {
      // 读取缓存中的当前列表数据
      const cachedData = cache.readQuery({ query: queryIndicatorsDocument });
      if (!cachedData) return;

      // 过滤掉被删除的项,更新缓存
      const updatedIndicators = cachedData.queryIndicators.filter(
        indicator => indicator.uuid !== uuid
      );

      cache.writeQuery({
        query: queryIndicatorsDocument,
        data: { queryIndicators: updatedIndicators }
      });
    }
  })
  .catch(info => console.log(info));
}

这样Apollo会自动更新缓存,useQuery会监听缓存变化并重新渲染组件,完全不需要手动处理state更新,自然也不会出现卸载后的state更新警告。

方案2:用AbortController取消异步请求

在组件卸载时取消未完成的mutation请求,避免回调函数执行。

import { useEffect, useRef } from 'react';

// 在组件内定义AbortController
const abortControllerRef = useRef(new AbortController());

useEffect(() => {
  const controller = abortControllerRef.current;
  // 组件卸载时取消请求
  return () => controller.abort();
}, []);

const handleDeleteIndicator = (uuid: any) => {
  deleteQueryIndicator({
    variables: { uuid },
    // 将signal传递给请求
    context: { fetchOptions: { signal: abortControllerRef.current.signal } }
  })
  .then(result => {
    const newList = displayedQueryIndicators.filter(
      indicator => indicator.uuid !== result.data.deleteQueryIndicator
    );
    setDisplayedQueryIndicators(newList);
  })
  .catch(info => {
    // 忽略请求被取消的错误
    if (info.name !== 'AbortError') {
      console.log(info);
    }
  });
}

方案3:用挂载状态标记控制state更新

维护一个标记组件是否挂载的ref,在异步回调执行前检查标记,仅当组件仍挂载时才更新state。

import { useEffect, useRef } from 'react';

const isMounted = useRef(true);

useEffect(() => {
  // 组件卸载时将标记设为false
  return () => {
    isMounted.current = false;
  };
}, []);

const handleDeleteIndicator = (uuid: any) => {
  deleteQueryIndicator({ variables: { uuid } })
  .then(result => {
    // 检查组件是否仍挂载
    if (!isMounted.current) return;
    
    const newList = displayedQueryIndicators.filter(
      indicator => indicator.uuid !== result.data.deleteQueryIndicator
    );
    setDisplayedQueryIndicators(newList);
  })
  .catch(info => console.log(info));
}

内容的提问来源于stack exchange,提问作者Anthony

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 11:42:34