本文转载自我的个人博客。
这是一篇译文,原文在这里。有兴趣的同学可以直接阅读原文,写的很好。图省事的同学可以直接看我的精简的译文。
相信很多react
初学者都遇到过以下两个警告(warnings)
:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Warning: Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
一般来说警告并不会让你的程序崩溃,但是它会降低程序的性能,比如我们上面提到的这两个警告就是这样。下面让我们来讨论一下这两个警告到底讲的是什么吧。
当一个已经被卸载(unmounted)
的组件调用setState()
方法时,你就会遇到以上两个警告。一般来说,有两种情况会导致组件的卸载:
- 通过条件渲染的组件,其渲染条件从达到变为没有达到,导致已渲染的组件的卸载
- 通过路由手段路由到别的组件,导致原组件被卸载
当组件被卸载后,被卸载的组件内的某些异步逻辑可能在组件被卸载后调用了setState()
企图更新组件内的state
,通常有三种场景可能会发生这种情况:
- 你对某个
API
提交了一个异步请求,组件在收到响应前就被卸载了,收到响应之后调用了setState()
企图更新state
,但这个时候组件早就被卸载了。 - 你为组件添加了监听事件,但是没有在
componentWillUnmount()
里移除这个监听事件,当组件移除以后监听的事件被触发。 - 你在类似于
setInterval()
的函数里调用了setState()
,但是没有在componentWillUnmount()
里移除这些函数。
那么在遇到以上两个警告时我们怎么样才能解决他们呢?
避免 intervals/listeners 在已卸载的组件中调用 setState()
对于上面提到的三种情况中的后两种情况,我们只需要在componentWillUnmount()
生命周期钩子里将intervals/listeners
移除就可以了。具体操作可以参考这个例子。
避免异步请求在已卸载的组件中调用 setState()
为了避免异步请求在已卸载的组件中调用setState()
,我们可以在请求收到响应后添加一个判断条件,判断此时组件有没有被卸载,如果没有卸载再继续执行之后的代码(比如setState()
)。具体实现如下:
class News extends Component {
//添加一个 class field 记录组件有没有被卸载,初始化为false,表示已卸载
_isMounted = false;
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
//组件在被挂载后将_isMounted的值更新为true,表示已挂载
this._isMounted = true;
axios
.get('https://hn.algolia.com/api/v1/search?query=react')
.then(result => {
//通过_isMounted判断组件有没有被卸载
if (this._isMounted) {
this.setState({
news: result.data.hits,
});
}
});
}
componentWillUnmount() {
//在组件被卸载时将_isMounted更新为false,表示组件已卸载
this._isMounted = false;
}
render() {
...
}
}
一个小插曲,关于class field
是什么,给大家两个例子,可以先体会下用class field
和不用的区别,以后有时间,我再写一篇专门关于class field
的文章。
不用class field
:
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
用class field
:
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}