如何在 useEffect 钩子反应中阻止内存泄漏
How to stop memory leak in useEffect hook react
我正在使用 Effect hook 从服务器获取数据并将这些数据传递给反应 table 在那里我使用相同的 api 调用从服务器加载下一组数据.
当应用程序加载时,我收到如下警告
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. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
效果挂钩:
useEffect(() => {
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
setPageLoading(false);
})
.catch((error: string) => {
toast.error(error);
setPageLoading(false);
});
}, []);
React Table 页:
<ReactTable
className="-striped -highlight"
columns={columns}
data={coursesData}
defaultPage={currentPage}
defaultPageSize={courses.perPage}
loading={isLoading}
manual={true}
onFetchData={setFilter}
/>
设置过滤功能:
const setFilter = (pagination: any) => {
props.dispatch(updateCoursePageSize(pagination.pageSize));
props.dispatch(updateCourseCurrentPage(pagination.page + 1));
setCurrentPage(pagination.page);
setPerPage(pagination.pageSize);
setLoading(true);
props.dispatch(fetchCourses()).then(() => {
setLoading(false);
});
};
有谁知道如何清理 react 中的钩子
使用 useEffect,您可以 return 一个将 运行 清理的函数。所以在你的情况下,你会想要这样的东西:
useEffect(() => {
let unmounted = false;
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
if (!unmounted) {
setPageLoading(false);
}
})
.catch((error: string) => {
if (!unmounted) {
toast.error(error);
setPageLoading(false);
}
});
return () => { unmounted = true };
}, []);
编辑:如果您需要一个在 useEffect 之外启动的调用,那么它仍然需要检查一个未安装的变量以判断它是否应该跳过对 setState 的调用。未安装的变量将由 useEffect 设置,但现在您需要克服一些障碍才能使变量在效果之外可访问。
const Example = (props) => {
const unmounted = useRef(false);
useEffect(() => {
return () => { unmounted.current = true }
}, []);
const setFilter = () => {
// ...
props.dispatch(fetchCourses()).then(() => {
if (!unmounted.current) {
setLoading(false);
}
})
}
// ...
return (
<ReactTable onFetchData={setFilter} /* other props omitted */ />
);
}
其他答案当然有用,我只是想分享一个我想出的解决方案。
我构建了这个 hook ,它的工作方式与 React 的 useState 一样,但只有在安装组件时才会设置状态。我发现它更优雅,因为您不必在组件中乱用 isMounted 变量!
安装:
npm install use-state-if-mounted
用法:
const [count, setCount] = useStateIfMounted(0);
您可以在钩子的 npm page 上找到更高级的文档。
内存泄漏发生,当一个不需要的并且应该从内存中清除的东西被保留下来,因为其他东西仍然持有它。在 React 组件的情况下,在组件中进行的异步调用可能会保留 setState 或其他引用的引用,并将保留它们直到调用完成。
您看到的警告来自 React,它说某些东西仍在保存并设置组件实例的状态,该组件实例早在组件卸载时就已从树中删除。现在使用不设置状态的标志只会删除警告但不会删除内存泄漏,即使使用 Abort 控制器也能做到这一点。为了避免这种情况,您可以使用状态管理工具来帮助分派一个动作,该动作将在组件外部进行处理,而无需保留组件的任何内存引用,例如 redux。如果您不使用此类工具,那么您应该找到一种方法来清除组件卸载时传递给异步调用的回调(然后,捕获,最后阻塞)。在下面的代码片段中,我正在做同样的事情,分离对传递给异步调用的方法的引用以避免内存泄漏。
这里的 Event Emitter 是一个 Observer,你可以创建一个或者使用一些包。
const PromiseObserver = new EventEmitter();
class AsyncAbort {
constructor() {
this.id = `async_${getRandomString(10)}`;
this.asyncFun = null;
this.asyncFunParams = [];
this.thenBlock = null;
this.catchBlock = null;
this.finallyBlock = null;
}
addCall(asyncFun, params) {
this.asyncFun = asyncFun;
this.asyncFunParams = params;
return this;
}
addThen(callback) {
this.thenBlock = callback;
return this;
}
addCatch(callback) {
this.catchBlock = callback;
return this;
}
addFinally(callback) {
this.finallyBlock = callback;
return this;
}
call() {
const callback = ({ type, value }) => {
switch (type) {
case "then":
if (this.thenBlock) this.thenBlock(value);
break;
case "catch":
if (this.catchBlock) this.catchBlock(value);
break;
case "finally":
if (this.finallyBlock) this.finallyBlock(value);
break;
default:
}
};
PromiseObserver.addListener(this.id, callback);
const cancel = () => {
PromiseObserver.removeAllListeners(this.id);
};
this.asyncFun(...this.asyncFunParams)
.then((resp) => {
PromiseObserver.emit(this.id, { type: "then", value: resp });
})
.catch((error) => {
PromiseObserver.emit(this.id, { type: "catch", value: error });
})
.finally(() => {
PromiseObserver.emit(this.id, { type: "finally" });
PromiseObserver.removeAllListeners(this.id);
});
return cancel;
}
}
在useEffect挂钩中你可以做
React.useEffect(() => {
const abort = new AsyncAbort()
.addCall(simulateSlowNetworkRequest, [])
.addThen((resp) => {
setText("done!");
})
.addCatch((error) => {
console.log(error);
})
.call();
return () => {
abort();
};
}, [setText]);
您可以像这样创建一个自定义挂钩:
import * as React from 'react';
export default function useStateWhenMounted<T>(initialValue: T) {
const [state, setState] = React.useState(initialValue);
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const setNewState = React.useCallback((value) => {
if (isMounted.current) {
setState(value);
}
}, []);
return [state, setNewState];
}
我正在使用 Effect hook 从服务器获取数据并将这些数据传递给反应 table 在那里我使用相同的 api 调用从服务器加载下一组数据. 当应用程序加载时,我收到如下警告
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. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
效果挂钩:
useEffect(() => {
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
setPageLoading(false);
})
.catch((error: string) => {
toast.error(error);
setPageLoading(false);
});
}, []);
React Table 页:
<ReactTable
className="-striped -highlight"
columns={columns}
data={coursesData}
defaultPage={currentPage}
defaultPageSize={courses.perPage}
loading={isLoading}
manual={true}
onFetchData={setFilter}
/>
设置过滤功能:
const setFilter = (pagination: any) => {
props.dispatch(updateCoursePageSize(pagination.pageSize));
props.dispatch(updateCourseCurrentPage(pagination.page + 1));
setCurrentPage(pagination.page);
setPerPage(pagination.pageSize);
setLoading(true);
props.dispatch(fetchCourses()).then(() => {
setLoading(false);
});
};
有谁知道如何清理 react 中的钩子
使用 useEffect,您可以 return 一个将 运行 清理的函数。所以在你的情况下,你会想要这样的东西:
useEffect(() => {
let unmounted = false;
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
if (!unmounted) {
setPageLoading(false);
}
})
.catch((error: string) => {
if (!unmounted) {
toast.error(error);
setPageLoading(false);
}
});
return () => { unmounted = true };
}, []);
编辑:如果您需要一个在 useEffect 之外启动的调用,那么它仍然需要检查一个未安装的变量以判断它是否应该跳过对 setState 的调用。未安装的变量将由 useEffect 设置,但现在您需要克服一些障碍才能使变量在效果之外可访问。
const Example = (props) => {
const unmounted = useRef(false);
useEffect(() => {
return () => { unmounted.current = true }
}, []);
const setFilter = () => {
// ...
props.dispatch(fetchCourses()).then(() => {
if (!unmounted.current) {
setLoading(false);
}
})
}
// ...
return (
<ReactTable onFetchData={setFilter} /* other props omitted */ />
);
}
其他答案当然有用,我只是想分享一个我想出的解决方案。 我构建了这个 hook ,它的工作方式与 React 的 useState 一样,但只有在安装组件时才会设置状态。我发现它更优雅,因为您不必在组件中乱用 isMounted 变量!
安装:
npm install use-state-if-mounted
用法:
const [count, setCount] = useStateIfMounted(0);
您可以在钩子的 npm page 上找到更高级的文档。
内存泄漏发生,当一个不需要的并且应该从内存中清除的东西被保留下来,因为其他东西仍然持有它。在 React 组件的情况下,在组件中进行的异步调用可能会保留 setState 或其他引用的引用,并将保留它们直到调用完成。 您看到的警告来自 React,它说某些东西仍在保存并设置组件实例的状态,该组件实例早在组件卸载时就已从树中删除。现在使用不设置状态的标志只会删除警告但不会删除内存泄漏,即使使用 Abort 控制器也能做到这一点。为了避免这种情况,您可以使用状态管理工具来帮助分派一个动作,该动作将在组件外部进行处理,而无需保留组件的任何内存引用,例如 redux。如果您不使用此类工具,那么您应该找到一种方法来清除组件卸载时传递给异步调用的回调(然后,捕获,最后阻塞)。在下面的代码片段中,我正在做同样的事情,分离对传递给异步调用的方法的引用以避免内存泄漏。 这里的 Event Emitter 是一个 Observer,你可以创建一个或者使用一些包。
const PromiseObserver = new EventEmitter();
class AsyncAbort {
constructor() {
this.id = `async_${getRandomString(10)}`;
this.asyncFun = null;
this.asyncFunParams = [];
this.thenBlock = null;
this.catchBlock = null;
this.finallyBlock = null;
}
addCall(asyncFun, params) {
this.asyncFun = asyncFun;
this.asyncFunParams = params;
return this;
}
addThen(callback) {
this.thenBlock = callback;
return this;
}
addCatch(callback) {
this.catchBlock = callback;
return this;
}
addFinally(callback) {
this.finallyBlock = callback;
return this;
}
call() {
const callback = ({ type, value }) => {
switch (type) {
case "then":
if (this.thenBlock) this.thenBlock(value);
break;
case "catch":
if (this.catchBlock) this.catchBlock(value);
break;
case "finally":
if (this.finallyBlock) this.finallyBlock(value);
break;
default:
}
};
PromiseObserver.addListener(this.id, callback);
const cancel = () => {
PromiseObserver.removeAllListeners(this.id);
};
this.asyncFun(...this.asyncFunParams)
.then((resp) => {
PromiseObserver.emit(this.id, { type: "then", value: resp });
})
.catch((error) => {
PromiseObserver.emit(this.id, { type: "catch", value: error });
})
.finally(() => {
PromiseObserver.emit(this.id, { type: "finally" });
PromiseObserver.removeAllListeners(this.id);
});
return cancel;
}
}
在useEffect挂钩中你可以做
React.useEffect(() => {
const abort = new AsyncAbort()
.addCall(simulateSlowNetworkRequest, [])
.addThen((resp) => {
setText("done!");
})
.addCatch((error) => {
console.log(error);
})
.call();
return () => {
abort();
};
}, [setText]);
您可以像这样创建一个自定义挂钩:
import * as React from 'react';
export default function useStateWhenMounted<T>(initialValue: T) {
const [state, setState] = React.useState(initialValue);
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const setNewState = React.useCallback((value) => {
if (isMounted.current) {
setState(value);
}
}, []);
return [state, setNewState];
}