在未挂载的组件上调用 setState

luk*_*az7 5 javascript promise reactjs

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

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

onSuccess功能

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

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

我的两个问题:

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

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

问题的现场示例:

handleSubmit() {
  this.setState({loading: true})
  someAsyncFunc()
    .then(() => {
      return this.props.onSuccess()
    })
    .finally(() => this.setState({loading: false}))
}
Run Code Online (Sandbox Code Playgroud)
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")
);
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper {
  max-height: 100% !important;
}
Run Code Online (Sandbox Code Playgroud)

lip*_*ipp 1

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

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

这实际上在反应文档中提到,它指向这个解决方案:https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6

如果您支持取消,您应该按照本文的鼓励在componentWillUnmountsomeAsyncFunction中执行此操作。但随后 - 当然 - 检查返回值并最终不调用。this.props.onSuccess