刷新当前页面时未调用 componentWillUnmount()

IT技术 reactjs
2021-03-26 17:26:16

我一直有这个问题,我的componentDidMount()方法中的代码在刷新当前页面(以及随后的组件)时没有正确触发。但是,只需通过单击链接导航和路由我的网站,它就可以正常工作。刷新当前页面?没有机会。

我发现问题是componentWillUnmount()当我刷新页面并触发精细点击链接和导航我的网站/应用程序时不会触发。

的触发componentWillUnmount()对我的应用程序至关重要,因为我在componentDidMount()方法中加载和处理的数据对于向用户显示信息非常重要。

我需要componentWillUnmount()在刷新页面时调用 ,因为在我的componentWillMount()函数中(每次刷新后都需要重新渲染)我做了一些简单的过滤并将该变量存储在状态值中,该状态值需要logos按顺序出现在状态变量中以便组件的其余部分工作。这不会在组件的生命周期中的任何时间更改或接收新值。

componentWillMount(){
  if(dataReady.get(true)){
    let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
      if(item.logo === true && item.location !== ""){
        return item;
      }
    }) : [];
    this.setState({logos: logos});
  }
};

悬崖:

  • 我在componentWillMount()方法中进行数据库过滤
  • 刷新后需要它出现在组件中
  • 但是我componentWillUnmount()遇到了刷新页面时不触发的问题
  • 需要帮忙
3个回答

当页面刷新时,react 没有机会像往常一样卸载组件。使用window.onbeforeunload事件设置刷新处理程序(阅读代码中的注释):

class Demo extends React.Component {
    constructor(props, context) {
        super(props, context);

        this.componentCleanup = this.componentCleanup.bind(this);
    }

    componentCleanup() { // this will hold the cleanup code
        // whatever you want to do when the component is unmounted or page refreshes
    }

    componentWillMount(){
      if(dataReady.get(true)){
        let logos = this.props.questions[0].data.logos.length > 0 ? this.props.questions[0].data.logos.filter((item) => {
          if(item.logo === true && item.location !== ""){
            return item;
          }
        }) : [];

        this.setState({ logos });
      }
    }

    componentDidMount(){
      window.addEventListener('beforeunload', this.componentCleanup);
    }

    componentWillUnmount() {
        this.componentCleanup();
        window.removeEventListener('beforeunload', this.componentCleanup); // remove the event handler for normal unmounting
    }

}

使用WindowUnloadEffect钩子

我已将代码提取到基于以下内容的可重用钩子useEffect

// The hook

const { useEffect, useRef, useState } = React

const useWindowUnloadEffect = (handler, callOnCleanup) => {
  const cb = useRef()
  
  cb.current = handler
  
  useEffect(() => {
    const handler = () => cb.current()
  
    window.addEventListener('beforeunload', handler)
    
    return () => {
      if(callOnCleanup) handler()
    
      window.removeEventListener('beforeunload', handler)
    }
  }, [callOnCleanup])
}


// Usage example
  
const Child = () => {
  useWindowUnloadEffect(() => console.log('unloaded'), true)
  
  return <div>example</div>
}

const Demo = () => {
  const [show, changeShow] = useState(true)

  return (
    <div>
      <button onClick={() => changeShow(!show)}>{show ? 'hide' : 'show'}</button>
      {show ? <Child /> : null}
    </div>
  )
}

ReactDOM.render(
  <Demo />,
  root
)
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

@BenBud - componentDidMount 和使用 addEventListener 的优点。我已经更新了答案。但是,需要注意的一件事是,您必须在 中使用相同的函数实例addEventListener,并removeEventListener实际删除事件处理程序,因此不要使用 2 个箭头函数。
2021-05-29 17:26:16
我的问题是刷新页面后组件没有卸载。所以componentWillUnmount甚至没有被调用。
2021-06-02 17:26:16
@OriDrori :这实际上应该使用事件侦听器来完成,以免覆盖您可能希望在卸载时触发的任何其他事件。此外,您不应该触发componentWillMount依赖于已经加载的 DOM 的事件,因为这会在服务器端渲染时中断 - 使用componentDidMountcomponentDidMount window.addEventListener("beforeunload", () => { this.componentCleanUp() }); componentWillUnmount window.removeEventListener("beforeunload", () => { this.componentCleanUp() });
2021-06-18 17:26:16
但是,window.onbeforeunload将被调用。window.onbeforeunload = nullcomponentWillUnmount仅仅是如果正常的路由发生移除事件。
2021-06-21 17:26:16
您需要beforeunload刷新事件处理程序,并且需要componentWillUnmount()处理未刷新的更改。另外componentWillUnmount()应该删除beforeunload以防止将来刷新调用它,而该组件不再存在。
2021-06-21 17:26:16

我也遇到了这个问题,并意识到我需要确保至少有 2 个组件将始终正常卸载。所以我终于做了一个高阶组件,确保包装的组件总是被卸载

import React, {Component} from 'react'

// this high order component will ensure that the Wrapped Component
// will always be unmounted, even if React does not have the time to
// call componentWillUnmount function
export default function withGracefulUnmount(WrappedComponent) {

    return class extends Component {

        constructor(props){
            super(props);
            this.state = { mounted: false };
            this.componentGracefulUnmount = this.componentGracefulUnmount.bind(this)
        }


        componentGracefulUnmount(){
            this.setState({mounted: false});

            window.removeEventListener('beforeunload', this.componentGracefulUnmount);
        }

        componentWillMount(){
            this.setState({mounted: true})
        }

        componentDidMount(){
            // make sure the componentWillUnmount of the wrapped instance is executed even if React
            // does not have the time to unmount properly. we achieve that by
            // * hooking on beforeunload for normal page browsing
            // * hooking on turbolinks:before-render for turbolinks page browsing
            window.addEventListener('beforeunload', this.componentGracefulUnmount);
        }

        componentWillUnmount(){
            this.componentGracefulUnmount()
        }

        render(){

            let { mounted }  = this.state;

            if (mounted) {
                return <WrappedComponent {...this.props} />
            } else {
                return null // force the unmount
            }
        }
    }

}

注意:如果像我一样,你正在使用 turbolinks 和 rails,你可能想要同时连接beforeunloadturbolinks:before-render事件。

我在 2 个项目中遇到了这个问题,所以我决定将代码提取到小包中:npmjs.com/package/react-graceful-unmount
2021-06-03 17:26:16

我看到这个问题有上千个观点,所以我来解释一下我是如何解决这个问题的:

要解决这个特殊问题,最明智的方法是创建一个加载订阅或数据库的上层组件,以便在将所需数据传递给子组件之前加载所需的数据,这将完全消除使用componentWillMount(). 此外,您可以在上层组件中进行计算,然后将它们作为props传递给您的接收组件中使用

例如:

class UpperLevelComponent extends React.Component {

render() {
if(this.props.isReady) {
return(<ChildComponent {...props}/>)
}
}
}

export default createContainer(() => {
const data = Meteor.subscribe("myData");
const isReady = data.ready();

return {
isReady,
data: MyData.find.fetch()
}
})

在上面的示例中,我使用 Meteor 的react式容器来获取我的 MongoDB 数据,并在渲染子组件之前等待它完全完成订阅,向它传递我想要的任何props。如果您在更高级别的组件中加载所有数据,您将不必依赖于componentWillMount()每次刷新后触发方法。数据将在上层组件中准备好,因此您可以在子组件中随意使用它。