抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

React 组件在销毁后,继续 setState 会造成很多问题,整理了几个解决方案供大家参考。

问题产生

React.createClass({
  // 组件挂载后执行请求
	componentDidMount: function () {
		const getData = () => {
			return new Promise((resolve, reject) => {
      	setTimeout(resolve,99999)
      }
		};
		
    getData()
			.then((data) => {
				this.setState({ list: data });
    })
  }
}

一种情况是:在组件挂载(mounted)之后进行了异步操作,比如ajax请求或者设置了定时器等,而你在callback中进行了setState操作。当你切换路由时,组件已经被卸载(unmounted)了,此时异步操作中callback还在执行,因此setState没有得到值

或者是另外一种情况:在 componentWillMount中执行异步操作,在回调中setState。但是当请求到达之前, 我们更换了页面或者移除了组件,就会报这个错误。这是因为虽然组件已经被移除,但是请求还在执行, 所以会报setState() on an unmounted component的错误。

总之:在组件销毁时仍有异步操作去 setState 则报错

官方解释:isMounted is an Antipattern – React Blog (reactjs.org)

解决方案

普通青年

class Component extends React.Component {
  componentDidMount() {
  	// use ajax, setTimeout, setInterval ... 
  }
  componentWillUnmount() {
  	// ajax.abort, clearTimeout, clearInterval
  }
}

2B青年

class Component extends React.Component {
  componentDidMount() {
    ajax()
      .then((data) => {
      	if (this.isMounted()) { // This is bad.
          this.setState({...});
        }
    })
  }
}

4B青年

class Component extends React.Component {
  componentDidMount() {
    ajax()
      .then((data) => {
        this.setState({...});
    })
  }
  componentWillUnmount() {
    this.setState = () => {}
  }
}

class Component extends React.Component {
  componentDidMount() {
    ajax()
      .then((data) => {
        this.setState({...});
    })
  }
	setState(state,callback){
  	if( this.isMounted() ){
      return super.setState.call(this, state, callback);
    }
    return () => {};
  }
}

纯属搞笑,官方的说法

Other uses of isMounted() are similarly erroneous; using isMounted() is a code smell because the only reason you would check is because you think you might be holding a reference after the component has unmounted.

上面这些做法不过是为了掩盖代码气味,你只是想让报错消失!

最好的办法就是:使用 Flux

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

An optimal solution would be to find places where setState() might be called after a component has unmounted, and fix them. Such situations most commonly occur due to callbacks, when a component is waiting for some data and gets unmounted before the data arrives. Ideally, any callbacks should be canceled in componentWillUnmount, prior to unmounting.

我猜官方的意思是把组件状态交给 Flux 处理,将数据流动交给专门的模块去管理,各应用部分分工明确,高度解耦。

flux overview

Action -> Dispatcher -> Store -> View

毕竟是工程化项目,说什么不至于上状态管理的,那你不如回原生,直接撸原生多快呀!

取消异步请求

Ajax

var xhr = new XMLHttpRequest(),
    method = "GET",
    url = "https://developer.mozilla.org/";
xhr.open(method, url, true);

xhr.send();

if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
  xhr.abort();
}

XMLHttpRequest.abort() - Web APIs | MDN (mozilla.org)

Fetch API

AbortController - Web API 接口参考 | MDN (mozilla.org)

axios

axios 源码系列之如何取消请求 - 知乎 (zhihu.com)

另外,结束 Promise 的写法,react 文档里也写了,不贴了。

参考文章:

在React组件unmounted之后setState的报错处理 - 最骚的就是你 - 博客园 (cnblogs.com)

解决React组件Unmount时依然setState报错问题 - SegmentFault 思否

评论