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; usingisMounted()
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 incomponentWillUnmount
, prior to unmounting.
我猜官方的意思是把组件状态交给 Flux 处理,将数据流动交给专门的模块去管理,各应用部分分工明确,高度解耦。
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)