React报错求助:已卸载组件调用setState,更新后异步取数遇问题
解决组件更新后异步请求完成时setState触发的内存泄漏警告
看起来你遇到的问题是:虽然你认为组件不会被销毁,但在componentDidUpdate里发起的异步请求可能在请求还没完成时,组件已经因为某些场景(比如路由跳转、父组件重新渲染导致该组件被移除)被卸载了,这时候再调用setState就会触发那个内存泄漏警告。
为什么会出现这个问题?
你在componentDidUpdate里发起的getGrades是异步操作,它需要等待服务器响应。如果在等待的过程中,组件被意外卸载了(比如用户跳转到其他页面),当请求最终完成时,你仍然会尝试给一个已经不存在的组件设置状态,这就触发了React的警告。
解决方案1:用挂载状态标志位控制setState
我们可以给组件加一个实例变量,用来跟踪组件是否还处于挂载状态,只有当组件还挂载时才执行setState:
class YourComponent extends React.Component { _isMounted = false; componentDidMount() { this._isMounted = true; } componentWillUnmount() { this._isMounted = false; } async componentDidUpdate(prevProps, prevState) { if (this.props.subject.length && prevProps.subject !== this.props.subject) { try { let result = await this.getGrades({ student: this.props.id, subject: this.props.subject }); // 只有组件还挂载时才更新状态 if (this._isMounted) { this.setState({ subject: this.props.subject, grades: result }); } } catch (error) { // 这里可以添加错误处理,同样要检查_isMounted if (this._isMounted) { // 比如设置错误状态 this.setState({ error: error.message }); } } } } async getGrades(params) { let response, body; if (params['subject'].length) { response = await fetch(apiRequestString.gradesBySubject(params)); } else { response = await fetch(apiRequestString.grades(params)); } body = await response.json(); if (response.status !== 200) throw Error(body.message); return body; } }
解决方案2:使用AbortController取消未完成的请求
React推荐的方式是取消异步任务,而不仅仅是跳过setState。Fetch API支持用AbortController来中止请求,这样请求完成后就不会执行后续的代码:
class YourComponent extends React.Component { abortController = null; componentWillUnmount() { // 如果有未完成的请求,中止它 if (this.abortController) { this.abortController.abort(); } } async componentDidUpdate(prevProps, prevState) { if (this.props.subject.length && prevProps.subject !== this.props.subject) { // 每次发起新请求前,先中止之前的请求(如果有的话) if (this.abortController) { this.abortController.abort(); } this.abortController = new AbortController(); const signal = this.abortController.signal; try { let response, body; if (this.props.subject.length) { response = await fetch(apiRequestString.gradesBySubject({ student: this.props.id, subject: this.props.subject }), { signal }); } else { response = await fetch(apiRequestString.grades({ student: this.props.id, subject: this.props.subject }), { signal }); } body = await response.json(); if (response.status !== 200) throw Error(body.message); this.setState({ subject: this.props.subject, grades: body }); } catch (error) { // 忽略中止请求的错误 if (error.name !== 'AbortError') { this.setState({ error: error.message }); } } } } }
选择哪个方案?
- 方案1简单易行,适合快速修复现有代码;
- 方案2更符合React的最佳实践,因为它真正取消了不必要的网络请求,节省带宽,也避免了无用的代码执行。
另外,你提到不需要销毁组件,但实际应用中组件的卸载场景可能比你想象的多,比如路由切换、条件渲染(父组件的state变化导致该组件被移除)等,所以处理这种异步请求的边界情况是很有必要的。
内容的提问来源于stack exchange,提问作者Nikola Stojaković
相关产品推荐
相关产品推荐

