在 Context.Consumer 创建的 useEffect 清理函数中取消所有订阅
Posted
技术标签:
【中文标题】在 Context.Consumer 创建的 useEffect 清理函数中取消所有订阅【英文标题】:Cancel all subscriptions in a useEffect cleanup function created by Context.Consumer 【发布时间】:2019-10-28 06:15:54 【问题描述】:每次onClick
执行时,我都会收到有关内存泄漏的警告消息。如何使用 useEffect
钩子从我的功能组件中的 Context.Consumer 取消订阅组件?
我没有找到取消订阅 AppContext 的方法。 AppContext.unsubsribe()
没用。
import React, useState, useContext from 'react';
import withRouter from 'react-router-dom';
import axios from 'axios';
import AppContext from "../context/AppContext";
const LoginPage = (props) =>
const [name, setName] = useContext(AppContext);
const [isLoading, setIsLoading] = useState(false);
const onClick = () =>
setIsLoading(true);
axios.post('/get-name')
.then(resp =>
setName(resp);
setIsLoading(false);
props.history.push('/');
)
.catch(err => console.log(err))
.finally(() => setIsLoading(false));
;
return (
<div>
<button onClick=onClick></button>
</div>
);
;
export default withRouter(LoginPage);
浏览器控制台中的错误消息:
警告:无法对未安装的对象执行 React 状态更新 零件。这是一个无操作,但它表明您的内存泄漏 应用。要修复,请取消所有订阅和异步任务 在 useEffect 清理函数中。 在 UserPage 中(由 Context.Consumer 创建) 在 Route 中(由 withRouter(UserPage) 创建) 在 withRouter(LoginPage) 中(由 Context.Consumer 创建) 在Route中(由UserRoute创建)
【问题讨论】:
问题真的出在 AppContext 上吗?我认为问题在于您在卸载组件后正在执行setIsLoading
。
我应该如何对setIsLoading
方法和isLoading
属性执行清理?
【参考方案1】:
你的问题是 axios 正在返回一个承诺,所以当组件被挂载时,它会在点击时执行axios.post(...)
。当它然后卸载时(而承诺仍然可能“未完成”),其 finally
的 setState
将在组件卸载后执行。
您可以使用简单的检查组件是否已安装:
import React, useState, useContext, useEffect from 'react';
import withRouter from 'react-router-dom';
import axios from 'axios';
import AppContext from "../context/AppContext";
const LoginPage = (props) =>
const [name, setName] = useContext(AppContext);
const [isLoading, setIsLoading] = useState(false);
const isMounted = useRef(null);
useEffect(() =>
// executed when component mounted
isMounted.current = true;
return () =>
// executed when unmount
isMounted.current = false;
, []);
const onClick = () =>
setIsLoading(true);
axios.post('/get-name')
.then(resp =>
setName(resp);
setIsLoading(false);
props.history.push('/');
)
.catch(err => console.log(err))
.finally(() =>
if (isMounted.current)
setIsLoading(false)
);
;
return (
<div>
<button onClick=onClick></button>
</div>
);
;
export default withRouter(LoginPage);
【讨论】:
【参考方案2】:如警告所述,在您的 UserPage
组件中,您需要对 useEffect
执行清理以避免内存泄漏。
参考Docs如何在效果后要求清理。
useEffect(() =>
function handleStatusChange(status)
setIsOnline(status.isOnline);
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () =>
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
;
);
【讨论】:
我在文档中看到了这个例子。对我来说,不清楚在我的情况下应该执行哪些方法。我没有订阅可以提供unsubscribe
方法的外部组件,例如ChatAPI.unsubscribeFromFriendStatus(...)
添加你的UserPage
,最好只做一个沙箱codesandbox.io/s/new【参考方案3】:
感谢@Bennet Dams,我可以解决我的问题,这是我按照他的示例编写的代码
const isMounted = useRef(null);
useEffect(() =>
isMounted.current = true;
fetchRequestsData();
return () =>
isMounted.current = false;
;
, []);
async function fetchRequestsData()
//My previous code which led to the warning
/* const data = await axios(
url: '../api/random/my-requests',
method: 'get',
);
setSuspendedRequests(data.suspended); */
let data;
axios
.get('../api/random/my-requests')
.then((resp) =>
data = resp.data;
)
.catch((err) => console.log(err))
.finally(() =>
if (isMounted.current)
setSuspendedRequests(data.suspended);
);
【讨论】:
以上是关于在 Context.Consumer 创建的 useEffect 清理函数中取消所有订阅的主要内容,如果未能解决你的问题,请参考以下文章
包含 Context.Provider 和 Context.Consumer 的 React Context 测试组件
是否可以更改 Context.Consumer 的显示名称?
React - 新的 Context API 不适用于 Class.contextType,但适用于 Context.Consumer
Class.contextType 和 Context.Consumer 之间的区别与工作示例