本文轉(zhuǎn)載自我的個人博客。
這是一篇譯文,原文在這里。有興趣的同學(xué)可以直接閱讀原文,寫的很好。圖省事的同學(xué)可以直接看我的精簡的譯文。
相信很多react初學(xué)者都遇到過以下兩個警告(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.
一般來說警告并不會讓你的程序崩潰,但是它會降低程序的性能,比如我們上面提到的這兩個警告就是這樣。下面讓我們來討論一下這兩個警告到底講的是什么吧。
當(dāng)一個已經(jīng)被卸載(unmounted)的組件調(diào)用setState()方法時,你就會遇到以上兩個警告。一般來說,有兩種情況會導(dǎo)致組件的卸載:
- 通過條件渲染的組件,其渲染條件從達(dá)到變?yōu)闆]有達(dá)到,導(dǎo)致已渲染的組件的卸載
- 通過路由手段路由到別的組件,導(dǎo)致原組件被卸載
當(dāng)組件被卸載后,被卸載的組件內(nèi)的某些異步邏輯可能在組件被卸載后調(diào)用了setState()企圖更新組件內(nèi)的state,通常有三種場景可能會發(fā)生這種情況:
- 你對某個
API提交了一個異步請求,組件在收到響應(yīng)前就被卸載了,收到響應(yīng)之后調(diào)用了setState()企圖更新state,但這個時候組件早就被卸載了。 - 你為組件添加了監(jiān)聽事件,但是沒有在
componentWillUnmount()里移除這個監(jiān)聽事件,當(dāng)組件移除以后監(jiān)聽的事件被觸發(fā)。 - 你在類似于
setInterval()的函數(shù)里調(diào)用了setState(),但是沒有在componentWillUnmount()里移除這些函數(shù)。
那么在遇到以上兩個警告時我們怎么樣才能解決他們呢?
避免 intervals/listeners 在已卸載的組件中調(diào)用 setState()
對于上面提到的三種情況中的后兩種情況,我們只需要在componentWillUnmount()生命周期鉤子里將intervals/listeners移除就可以了。具體操作可以參考這個例子。
避免異步請求在已卸載的組件中調(diào)用 setState()
為了避免異步請求在已卸載的組件中調(diào)用setState(),我們可以在請求收到響應(yīng)后添加一個判斷條件,判斷此時組件有沒有被卸載,如果沒有卸載再繼續(xù)執(zhí)行之后的代碼(比如setState())。具體實現(xiàn)如下:
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() {
...
}
}
一個小插曲,關(guān)于class field是什么,給大家兩個例子,可以先體會下用class field和不用的區(qū)別,以后有時間,我再寫一篇專門關(guān)于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++;
}
}