【问题标题】:Calling setState on an unmounted component在未安装的组件上调用 setState
【发布时间】:2018-11-15 22:50:20
【问题描述】:

在我的很多组件中,我需要做这样的事情:

handleSubmit() {
  this.setState({loading: true})
  someAsyncFunc()
    .then(() => {
      return this.props.onSuccess()
    })
    .finally(() => this.setState({loading: false}))
}

onSuccess 函数

  • 可能是也可能不是一个承诺(如果是,loading 应该保持真实,直到它被解决)
  • 可能会或可能不会卸载组件(它可能会关闭此组件所在的模式,甚至导航到不同的页面)

如果函数卸载组件,this.setState({loading: false})显然会触发警告Can't call setState (or forceUpdate) on an unmounted component.

我的两个问题:

  1. 是否有避免该问题的简单方法?我不想在componentDidMountcomponentWillUnmount 中设置一些_isMounted 变量,然后在我的大多数组件中需要时检查它,另外我可能会在下次写这样的东西时忘记这样做......李>
  2. 真的有问题吗?我知道,根据警告it indicates a memory leak in my application,但在这种情况下它不是内存泄漏,是吗?也许忽略警告就可以了...

编辑:第二个问题对我来说比第一个问题更重要。如果这确实是一个问题,并且我不能在未安装的组件上调用setState,我可能会自己找到一些解决方法。但是我很好奇我是否不能忽略它。

问题的实例:

const someAsyncFunc = () => new Promise(resolve => {
  setTimeout(() => {
  	console.log("someAsyncFunc resolving");
    resolve("done");
  }, 2000);
});

class Example extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {loading: false};
  }
  
  componentDidMount() {
    setTimeout(() => this.handleSubmit(), 100);
  }
  
  handleSubmit() {
    this.setState({loading: true})
    someAsyncFunc()
      /*
      .then(() => {
        return this.props.onSuccess()
      })
      */
      .finally(() => this.setState({loading: false}))
  }
  
  render() {
    return <div>{String(this.state.loading)}</div>;
  }
}

class Wrapper extends React.Component {
	constructor(props, ...rest) {
  	super(props, ...rest);
    this.state = {
    	children: props.children
    };
  }
	componentDidMount() {
  	setTimeout(() => {
    	console.log("removing");
    	this.setState({children: []});
    }, 1500)
  }
	render() {
  	return <div>{this.state.children}</div>;
  }
}

ReactDOM.render(
	<Wrapper>
    <Example />
	</Wrapper>,
  document.getElementById("root")
);
.as-console-wrapper {
  max-height: 100% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>

【问题讨论】:

  • 在组件模态关闭和导航中,为什么需要加载false?反正与之关联的状态会被销毁
  • @RIYAJKHAN 正如我所写:onSuccess 函数可能会或可能不会卸载组件 - 在某些情况下,模态应该保持打开状态,当然不会加载.. .

标签: javascript reactjs promise


【解决方案1】:

不幸的是,您必须自己跟踪“isMounted”。 为了简化您的控制流程,您可以使用async/await:

handleSubmit() {
  this.setState({loading: true})
  try {
    await someAsyncFunction()
    await this.props.onSuccess()
  } finally {
    if (this._isMounted) {
      this.setState({loading: false})
    }
  }
}

react docs中其实有提到这个,指向这个解决方案:https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6

如果您的someAsyncFunction 支持取消,您应该在componentWillUnmount 中这样做,正如this article 所鼓励的那样。但随后 - 当然 - 检查返回值并最终不调用 this.props.onSuccess

【讨论】:

  • 赞成,因为您在技术上是正确的,但如果我不能忽略警告,我实际上会更感兴趣。我更新了问题...
  • 谢谢。我认为你永远不应该容忍警告。在这方面,新版本的 React 可能会变得更加严格。
【解决方案2】:

您应该可以使用this._isMounted 来检查组件是否实际安装。

handleSubmit() {
  this.setState({loading: true})
  someAsyncFunc()
    .then(() => {
      return this.props.onSuccess()
    })
    .finally(() => {
      if (this && this._isMounted) { // check if component is still mounted
        this.setState({loading: false})
      }
    })
}

但请注意,这种方法被认为是一种反模式。 https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

【讨论】:

  • 所以this 在那个阶段已经是undefined 了?你也可以检查一下。我更新了我的答案。
  • 不,请检查您在答案中链接的文章的最后一行:Getting rid of isMounted() makes it one step easier for you to upgrade to ES6 classes, where using isMounted() is already prohibited。它只是不再反应了。
【解决方案3】:

怎么样

componentWillUnmount() {  
    // Assign this.setState to empty function to avoid console warning
    // when this.setState is called on an unmounted component
    this.setState = () => undefined;
}

【讨论】:

    【解决方案4】:
    class myClass extends Component {
      _isMounted = false;
    
      constructor(props) {
        super(props);
    
        this.state = {
          data: [],
        };
      }
    
      componentDidMount() {
        this._isMounted = true;
        this._getData();
      }
    
      componentWillUnmount() {
        this._isMounted = false;
      }
    
      _getData() {
        axios.get('example.com').then(data => {
          if (this._isMounted) {
            this.setState({ data })
          }
        });
      }
    
    
      render() {
        ...
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-12-30
      • 2018-11-19
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 2015-12-30
      • 2017-03-10
      • 2018-10-28
      • 1970-01-01
      相关资源
      最近更新 更多