See error logs in your console with the message “Cannot perform state update on an unmounted component” from your React application? There is a simple cause and easy fix.
在您的React应用程序中查看控制台中的错误日志,并显示消息“无法在已卸载的组件上执行状态更新”? 原因很简单,也很容易解决。
原因 (The Cause)
React components which run asynchronous operations and perform state updates can cause memory leaks if state updates are made after the component is unmounted. Here is a common scenario where this could pop up:
如果在卸载组件后进行状态更新,则运行异步操作并执行状态更新的React组件可能会导致内存泄漏。 这是一个可能弹出的常见情况:
- User performs an action triggering an event handler to fetch data from an API. 用户执行触发事件处理程序以从API提取数据的操作。
- The user clicks on a link, navigating them to a different page, before (1) completes. 用户单击链接,然后在(1)完成之前将其导航到其他页面。
- The event handler from (1) completes the fetch, and calls a state setter function, passing it the data that was retrieved from the API. (1)中的事件处理程序完成了提取,并调用了状态设置器函数,并将从API检索到的数据传递给该函数。
Since the component was unmounted, a state setter function is being called in a component that is no longer mounted. Essentially, the setter function is updating state no longer exists. Memory Leak.
由于已卸载组件,因此将在不再安装的组件中调用状态设置器功能。 本质上,setter函数正在更新的状态不再存在。 内存泄漏。
Here is a contrived example of unsafe code:
这是不安全代码的虚构示例:
const [value, setValue] = useState({});
useEffect(() => {
const runAsyncOperation = () => {
setTimeout(() => {
// MEMORY LEAK HERE, COMPONENT UNMOUNTED
setValue({ key: 'value' });
}, 1000);
}
runAsyncOperation();
// USER NAVIGATES AWAY FROM PAGE HERE,
// IN LESS THAN 1000 MS
}, []);
修复 (The Fix)
Make sure that the component is still mounted before attempting to update state. We can use the useEffect cleanup function to track the mounted status of the component.
在尝试更新状态之前,请确保该组件仍已安装。 我们可以使用useEffect清理功能来跟踪组件的安装状态。
useEffect(() => {
let isMounted = true;
return () => {
isMounted = false;
}
}, [])
*Any function returned from a useEffect callback will be called when the component is unmounted
*卸载组件时,将调用useEffect回调返回的任何函数
A fix for said contrived example:
修复上述人为的示例:
const [value, setValue] = useState({});
useEffect(() => {
let isMounted = true; const runAsyncOperation = () => {
setTimeout(() => {
// NO MORE MEMORY LEAK, setValue WON'T
// BE CALLED IF COMPONENT WAS UNMOUNTED
if (isMounted) {
setValue({ key: 'value' });
}
}, 1000);
}
runAsyncOperation(); return () => {
isMounted = false;
}
}, []);
This works, but it’s a little ugly, and even uglier if you have it in several (if not more) places in your codebase.
这可以工作,但是如果在代码库中的多个(如果不是更多的话)位置使用它,则会有些丑陋,甚至更丑陋。
For a cleaner solution, use a custom react hook that encapsulates this logic:
对于更干净的解决方案,请使用封装以下逻辑的自定义react挂钩:
import { useState, useEffect, useRef } from 'react'const useSafeState = (initialValue) => {
const isMountedRef = useRef(true);
const [currentValue, setCurrentValue] = useState(initialValue); useEffect(() => {
return () => {
isMountedRef.current = false;
}
}, [isMountedRef]); const setSafeState = (value) => {
if (isMountedRef && isMountedRef.current) {
setCurrentValue(value);
}
} return [currentValue, setSafeState];
}export default useSafeState;
Usage within a component is exactly like using regular state:
组件内的用法与使用常规状态完全相同:
const [value, setValue] = useSafeState({});
useEffect(() => {
const runAsyncOperation = () => {
setTimeout(() => {
// NO MEMORY LEAK
setValue({ key: 'value' });
}, 1000);
}
runAsyncOperation();
// USER NAVIGATES AWAY FROM PAGE HERE,
// IN LESS THAN 1000 MS
}, []);
Hopefully this helps you with those annoying console error messages and sneaky memory leaks!
希望这可以帮助您解决那些烦人的控制台错误消息和偷偷摸摸的内存泄漏!
NOTE: This custom hook does not address potential race conditions when an effect is triggered repeatedly from changes in the dependency array. It is really intended only for preventing unmounted component errors. For preventing race conditions, you would need to use the “less clean” approach above, which is difficult to break out into a custom hook.
注意:当依赖项数组中的更改反复触发效果时,此自定义挂钩无法解决潜在的竞争条件。 它实际上仅用于防止未安装的组件错误。 为了防止出现比赛情况,您需要使用上面的“不太干净”的方法,很难将其分解为自定义的挂钩。