无法对已卸载的组件执行 React 状态更新:如何修复并优化代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

每个 React 开发者都讨厌的熟悉 Bug

\n

您是否曾在浏览器控制台中看到过这样的警告 Warning: 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.?如果是——您并不孤单。这个问题在 Stack Overflow 上已经获得了 数千次浏览,并成为 React 新手和经验丰富的开发者的真正痛点。

\n

在本文中,我们将分析为什么会出现这个错误,如何使用 TypeScript 修复它,最重要的是——如何优化您应用程序的性能,使此类警告不再出现。开始吧!

\n\n

为什么 React 会报错 unmounted component?

\n

想象一下:用户打开了一个页面,开始加载数据(例如通过 fetch 或 axios),但没有等待响应就切换到了另一个屏幕。发起请求的组件已经被卸载(从 DOM 中移除),但异步操作仍在执行。当响应到达时,代码试图更新一个已经不存在的组件的状态(state)。React 理所当然地抗议:“嘿,我无法更新不存在的东西!”。

\n

在 TypeScript 中,如果您使用严格的类型检查并且忘记了 useEffect 中的 cleanup 函数,这个问题尤其明显。

\n\n

问题的经典示例 (TypeScript)

\n
import React, { useState, useEffect } from 'react';\n\ninterface User {\n  id: number;\n  name: string;\n}\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n\n  useEffect(() => {\n    fetch(`/api/users/${userId}`)\n      .then(res => res.json())\n      .then(data => setUser(data)); // 错误!组件可能已被卸载\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

如果用户快速切换路由,UserProfile 组件将被移除,而 setUser 会在空组件上被调用。结果导致内存泄漏和控制台错误。

\n\n

修复错误的 3 种方法(附 TypeScript 示例)

\n

让我们看看三种可靠的方法,它们可以解决这个问题并提高React 应用程序的性能。

\n\n

方法 1:使用 useRef 的标志(最流行)

\n

创建一个 ref 标志,指示组件是否已挂载。仅当标志为 true 时才更新状态。

\n
import React, { useState, useEffect, useRef } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n  const isMounted = useRef(true);\n\n  useEffect(() => {\n    // 每次挂载时重置标志\n    isMounted.current = true;\n\n    fetch(`/api/users/${userId}`)\n      .then(res => res.json())\n      .then(data => {\n        if (isMounted.current) {\n          setUser(data);\n        }\n      });\n\n    // 清理:卸载时将标志设置为 false\n    return () => {\n      isMounted.current = false;\n    };\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

优点:简单,不需要额外的库。缺点:需要记住进行清理。

\n\n

方法 2:使用 AbortController 取消 fetch 请求(推荐)

\n

现代且正确的方法是取消请求本身,而不仅仅是忽略响应。这可以节省服务器和客户端资源。

\n
import React, { useState, useEffect } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    const controller = new AbortController();\n    const signal = con                

博客

书籍推荐