【问题标题】:How can I check if the component is unmounted in a functional component?如何检查组件是否已卸载到功能组件中?
【发布时间】:2019-12-28 15:31:54
【问题描述】:

回调函数设置组件状态。但有时提供数据的订阅需要结束。因为回调是异步执行的,所以它不知道订阅是否在服务调用(它执行回调函数)之后结束。

然后我在控制台中看到以下错误:

警告:无法对未安装的组件执行 React 状态更新。 这是一个空操作,但它表明您的应用程序中存在内存泄漏。 要修复,请取消 useEffect 中的所有订阅和异步任务 清理功能。

有没有办法访问组件状态,即使我在回调函数中?

这将是步骤:

  • 带参数订阅
  • 退订
  • 组件已卸载
  • 订阅的服务执行回调函数
  • 回调函数在未安装的组件中设置状态,并在上面给出错误

【问题讨论】:

标签: reactjs


【解决方案1】:

你可以使用这样的引用:

const mounted = useRef(false);

useEffect(() => {
    mounted.current = true;

    return () => { mounted.current = false; };
}, []);

然后在你的回调中你可以检查mounted.current === false 并避免设置状态

【讨论】:

  • 非常干净和简单的解决方案!
【解决方案2】:

这里有一些伪代码,您可以使用useEffect 查看组件是否已挂载。

它使用 useEffect 来侦听someService,当它收到消息时它会检查组件是否已挂载(组件卸载时也会调​​用清理函数),如果是,则使用由useState 创建的setServiceMessage设置服务接收的消息:

import { useState, useEffect } from 'react';
import someService from 'some-service';

export default props => {
  const userId = props.userId;
  const [serviceMessage, setServiceMessage] = useState([]);
  useEffect(
    () => {
      const mounted = { current: true };
      someService.listen(
        //listen to messages for this user
        userId, 
        //callback when message is received
        message => {
          //only set message when component is mounted
          if (mounted.current) {
            setServiceMessage(serviceMessage.concat(message));
          }
      });
      //returning cleanup function
      return () => {
        //do not listen to the service anymore
        someService.stopListen(userId);
        //set mounted to false if userId changed then mounted
        //  will immediately be set to true again and someService
        //  will listen to another user's messages but if the 
        //  component is unmounted then mounted.current will 
        //  continue to be false
        mounted.current = false;
      };
    },//<-- the function passed to useEffects
    //the function passed to useEffect will be called
    //every time props.userId changes, you can pass multiple
    //values here like [userId,otherValue] and then the function
    //will be called whenever one of the values changes
    //note that when this is an object then {hi:1} is not {hi:1}
    //referential equality is checked so create this with memoization
    //if this is an object created by mapStateToProps or a function
    [userId]
  );
};

【讨论】:

    【解决方案3】:

    我发现这个问题的公认答案很难阅读,React 提供了他们自己的documentation on just this question。他们的例子是:

    import React, { useState, useEffect } from 'react';
    
    function FriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      useEffect(() => {
        function handleStatusChange(status) {
          setIsOnline(status.isOnline);
        }
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        // Specify how to clean up after this effect:
        return function cleanup() {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }
    

    我创建了一个名为&lt;Fade&gt; 的组件,它将淡入/淡出它给定的任何子元素。请注意,它依赖于 bootstrap 的 .fade.show 类,尽管这些可以在没有 bootstrap 的情况下轻松实现。

    const Fade: React.FC<{}> = ({ children }) => {
      const [ className, setClassName ] = useState('fade')
      const [ newChildren, setNewChildren ] = useState(children)
    
      useEffect(() => {
        setClassName('fade')
    
        const timerId = setTimeout(() => {
          setClassName('fade show')
          setNewChildren(children)
        }, TIMEOUT_DURATION)
    
        return () => {
          clearTimeout(timerId)
        }   
    
      }, [children])
    
      return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
    }
    

    这一切都归结为一条规则:useEffect返回的清理函数中取消订阅您的异步任务

    【讨论】:

      【解决方案4】:

      您可以从 useEffect 返回一个函数,该函数将在卸载功能组件时触发。 Please read this

      import React, { useEffect } from 'react';
      const ComponentExample = () => {
          useEffect(() => {
              // Anything in here is fired on component mount.
              return () => {
                  // Anything in here is fired on component unmount.
              }
          }, [])
      }
      

      【讨论】:

        【解决方案5】:

        这个钩子(来自 Mohamed 的回答)以更优雅的方式解决了这个问题:

        function useMounted() {
          const mounted = useMemo(() => ({ current: true }), []);
          useEffect(() => {
            return () => { mounted.current = false}
          }, [mounted]);
          return mounted;
        }
        

        (更新为使用 useMemo 而不是 useRef 以提高可读性)。

        【讨论】:

        • 这个解决方案看起来很干净,但我不明白 useRef 在这种情况下的用户。
        • 我已经更新了代码示例。它不再使用 useRef。
        猜你喜欢
        • 2019-01-20
        • 1970-01-01
        • 2017-02-07
        • 2021-03-13
        • 2016-03-23
        • 2017-12-23
        • 2021-01-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多