【问题标题】:Is there a way to check if the react component is unmounted?有没有办法检查反应组件是否已卸载?
【发布时间】:2017-02-07 14:42:46
【问题描述】:

我有一个用例需要卸载我的反应组件。但在某些情况下,特定的 react 组件会被不同的函数卸载。 因此,我需要在卸载之前检查组件是否已安装。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    建议您使用 useRef 挂钩来跟踪组件是否已安装,因为每当您更新状态时,react 都会重新渲染整个组件以及它将触发 useEffect 或其他钩子的执行。

    function MyComponent(props: Props) {
      const isMounted = useRef(false)
    
      useEffect(() => {
        isMounted.current = true;
        return () => { isMounted.current = false }
      }, []);
    
      return (...);
    }
    
    export default MyComponent;
    

    然后检查组件是否使用if (isMounted.current) ... 挂载

    【讨论】:

    • 对类组件使用stackoverflow.com/a/39767963/1783174。将其用于钩子。
    • 你的意思是说你推荐使用useState而不是“不”?因为您的解决方案似乎使用它
    • 如果您在单个组件中有多个 useEffects,则将调用清理函数,因此这不起作用,因为重新渲染和重新安装之间没有区别。
    • “另外,如果一个组件渲染多次(通常是这样),前一个效果会在执行下一个效果之前被清除。” reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
    【解决方案2】:

    使用@DerekSoike 回答,但是在我的情况下,使用useState 来控制挂载状态不起作用,因为状态在不需要时复活了

    对我有用的是使用单个变量

    myFunctsetTimeout中被调用,我的猜测是,当同一个组件在另一个页面中初始化钩子时,它恢复了导致内存泄漏再次出现的状态

    所以这对我不起作用

      const [isMounted, setIsMounted] = useState(false)
    
      useEffect(() => {
        setIsMounted(true)
        return () => setIsMounted(false)
      }, [])
    
      const myFunct = () => {
        console.log(isMounted) // not always false
        if (!isMounted) return
        // change a state
      }
    

    这确实对我有用

      let stillMounted = { value: false }
      useEffect(() => {
        stillMounted.value = true
        return () => (stillMounted.value = false)
      }, [])
    
      const myFunct = () => {
        if (!stillMounted.value) return
        // change a state
      }
    

    【讨论】:

    • 这有效,而之前的策略(在许多地方更新)没有...谢谢@GWorking
    • 如果我们将 isMounted 状态更新为 true 或 false,它将重新加载功能组件,我建议使用 let 变量。
    【解决方案3】:

    如果你使用钩子:

    function MyComponent(props: Props) {
      const [isMounted, setIsMounted] = useState<boolean>(false);
    
      useEffect(() => {
        setIsMounted(true);
      }, []);
    
      useEffect(() => {
        return () => {
          setIsMounted(false);
        }
      }, []);
    
      return (...);
    }
    
    export default MyComponent;
    

    【讨论】:

    • 这篇文章dev.to/trentyang/replace-lifecycle-with-hooks-in-react-3d4n 将有助于提供更多关于如何从 React Native Class 风格转换为 React Hooks 风格的细节
    • @ZiaUlRehmanMughal 第一个useEffect 挂钩在组件挂载时调用一次,因为一个空数组作为依赖项列表传递。第二个useEffect 钩子传递了一个清理函数,该函数在卸载时被调用。查看文档了解更多详情:reactjs.org/docs/hooks-reference.html#useeffect -
    • 这是内存泄漏。您正在对已卸载的组件调用 setState。我建议人们使用其他answer
    【解决方案4】:

    由于isMounted() 已被正式弃用,您可以在您的组件中执行此操作:

    componentDidMount() { 
      this._ismounted = true;
    }
    
    componentWillUnmount() {
       this._ismounted = false;
    }
    

    这种维护自己的state 变量的模式在 ReactJS 文档中详细说明:isMounted is an Antipattern

    【讨论】:

    • 感谢来自文档的提示...只需在 componentDidMount 中将 _isMounted 属性设置为 true 并在 componentWillUnmount 中将其设置为 false ...但是,使用 this.setState({mounted:错误的});可能为时已晚而无法阻止警告,因为状态更改不会立即发生 - 最好为此使用类属性 ... this.mounted = false - 谢谢,这为我指明了正确的方向
    • @danday74,是的,你是对的。当我写答案时,我可能错过了重点。如果有帮助,请考虑为答案投票
    • @KevinGhaboosi,不幸的是,只有提出这个问题的人才能接受这个答案
    【解决方案5】:

    你可以使用:

    myComponent.updater.isMounted(myComponent)
    

    "myComponent" 是你的反应组件的实例。 如果组件已安装,则返回“true”,如果未安装,则返回“false”..

    • 这是不支持的方法。你最好取消订阅任何异步/事件 在 componentWillUnmount 上。

    【讨论】:

    • 谢谢,这有助于我进行调试。
    【解决方案6】:

    相同的想法,但另一个实现

    /**
     * component with async action within
     * 
     * @public
     */
    class MyComponent extends Component {
        constructor ( ...args ) {
            // do not forget about super =)
            super(...args);
            // NOTE correct store "setState"
            let originSetState = this.setState.bind(this);
            // NOTE override
            this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
        }
        /**
         * no necessary setup flag on component mount
         * @public
         */
        componentWillUnmount() {
            // NOTE setup flag
            this.isUnmounted = true;
        }
        /**
         *
         * @public
         */
        myCustomAsyncAction () {
            // ... code
            this.setState({any: 'data'}); // do not care about component status
            // ... code
        }
    
        render () { /* ... */ }
    }

    【讨论】:

      【解决方案7】:

      另一种解决方案是使用 Refs 。如果您使用的是 React 16.3+,请在渲染函数中对您的顶级项目进行引用。

      然后简单地检查 ref.current 是否为空。

      例子:

      class MyClass extends React.Component {
        constructor(props) {
          super(props);
          this.elementRef = React.createRef();
        }
      
        checkIfMounted() {
           return this.elementRef.current != null;
        }
      
        render() {
          return (
            <div ref={this.elementRef} />
          );
        }
      }
      

      【讨论】:

        【解决方案8】:

        我来到这里是因为我正在寻找一种方法来阻止 polling API。

        react docs 确实涵盖了 websocket 案例,但不包括轮询案例。

        我的工作方式

        // React component
        
        React.createClass({
            poll () {
                if (this.unmounted) {
                    return
                }
                // otherwise, call the api
            }
            componentWillUnmount () {
                this.unmounted = true
            }
        })
        

        它有效。希望对你有帮助

        如果你们知道任何失败的测试用例,请告诉我=]

        【讨论】:

        • 它可以工作,但是如果你有多个实例呢? all 将引用相同的 allowPolling
        • 是的,你最好不过在实例中,我将编辑代码 sn-p。
        【解决方案9】:

        我认为 Shubham 的回答是 react 为需要转换代码以停止使用 isMounted 反模式的人们建议的解决方法。

        这不一定是坏事,但值得列出这个问题的真正解决方案。

        The article linked by Shubham 提供 2 条建议来避免这种反模式。您需要的取决于您在卸载组件时调用 setState 的原因。

        如果您在组件中使用 Flux 存储,则必须在 componentWillUnmount 中取消订阅

        class MyComponent extends React.Component {
          componentDidMount() {
            mydatastore.subscribe(this);
          }
          render() {
            ...
          }
          componentWillUnmount() {
            mydatastore.unsubscribe(this);
          }
        }
        

        如果您使用 ES6 Promise,您可能需要包装您的 Promise 以使其可取消。

        const cancelablePromise = makeCancelable(
          new Promise(r => component.setState({...}}))
        );
        
        cancelablePromise
          .promise
          .then(() => console.log('resolved'))
          .catch((reason) => console.log('isCanceled', reason.isCanceled));
        
        cancelablePromise.cancel(); // Cancel the promise
        

        在链接文章中了解更多关于 makeCancelable 的信息。

        总之,不要试图通过设置变量和检查组件是否已挂载来修补此问题,而是要找到问题的根源。如果您能提出任何其他常见案例,请发表评论。

        【讨论】:

        • 想象一个帖子列表。每个帖子都是一个单独的组件,并且旁边(内部)有一个删除按钮。当用户点击按钮时,post 设置isDeleting = true(禁用按钮),告诉父组件删除帖子(使用通过 props 传递的回调),当他完成时(另一个回调,但到 post 组件)它可能需要启用按钮。例如。发生 HTTP 错误时(帖子尚未删除)。也就是说,post 组件可能需要根据它是否仍然挂载来更改状态。这有什么问题吗?
        • 只是为了指出仍然来这里的人。参考文章中的makeCancelable 方法是设置一个局部变量——就像Shubham 的例子一样。没有办法真正取消 Promise。我很难理解为什么在其他地方设置一个布尔值比在 React 组件中清楚地设置它更好。不过,关于取消订阅的观点是有效的。
        • 我的组件在对象的属性上设置了代理。当该对象更新时,代理会调用setState,以便属性和组件的状态同步。所以在这种情况下,这些建议是无关紧要的。
        【解决方案10】:

        我发现组件会被卸载,生成填充这个var

        if(!this._calledComponentWillUnmount)this.setState({vars});

        【讨论】:

        • 这是一个内部属性,我认为与它们一起玩并不安全,因为它将来可能会改变。
        • 好吧,老实说,在你更新库之前它是安全的,所以在某些情况下,这是一个很好的解决方案。但应该有一个条件!== undefined 以确保该属性存在。
        • 当然,最好还是听从官方的建议。
        猜你喜欢
        • 2016-07-15
        • 2020-10-15
        • 1970-01-01
        • 1970-01-01
        • 2019-01-20
        • 2023-04-04
        • 2020-11-25
        • 2020-04-24
        相关资源
        最近更新 更多