在 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(...)。当它然后卸载时(而承诺仍然可能“未完成”),其 finallysetState 将在组件卸载后执行。

您可以使用简单的检查组件是否已安装:

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 之间的区别与工作示例

包含Context.Provider和Context.Consumer的反应上下文测试组件

react--context,高阶组件,hook