React-Native中的組件加載、卸載與setState問題。
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.
通常我們會在componentWillMount方法中執行異步數據請求,然后調用setState方法處理得到的數據來更新界面。有時會遇到這個警告,如下圖:

警告提示我們可能在被卸載的組件上調用了setState()方法。一般情況下是在某個異步請求還未結束時組件被卸載了,但請求完畢后按照仍會按照正常流程調用setState()方法,這時就出現了上面的警告。
下面用一段代碼來復現這種情況。為了方便,不使用真正的網絡請求,而是使用下面的count()方法。count()方法每隔1秒調用一次setState方法將this.state.count加1,總共調用10次setState()方法。這樣就有充足的時間來觀察和操作。
在render()方法中,將this.state.count這個數字顯示在屏幕中央。componentWillMount方法中調用count方法,組件准備加載時就開始計數,相當於進行異步的網絡請求。在跳轉到這個頁面后,屏幕中央的數字會從0一直加到10,在加到10之前,退出這個頁面,就能觀察到上圖所示的警告。
import React, { Component} from 'react';
import {
View,
Text,
} from 'react-native';
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
}
}
x = 0;
count = () => {
if (this.x < 10) {
this.x++;
this.setState({count: this.x});
setTimeout(this.count, 1000);
}
}
componentWillMount() {
this.count();
}
render() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 40}}>{this.state.count}</Text>
</View>
)
}
}
實際上,這個警告能幫助我們找到程序中的bug,在一個被卸載的組件上調用setState()意味着這個組件可能沒有被正確的清理掉,也就是說,程序仍然具有對這個被卸載組件的引用,這可能導致內存泄漏。
以前有一個isMounted()函數用來確定組件的狀態,避免在被卸載了的組件上調用setState()方法,但是現在已經不再使用了。
要解決這個問題,可以維護一個變量_isMounted,來跟蹤組件的狀態。在componentDidMount()中將其設置為true,在componentWillUnmount()中將其設置為false。然后在使用setState, replaceState, or forceUpdate方法時檢查組件被卸載之后是否可能被調用,如果是,則使用_isMounted變量。
修正后的代碼如下。現在再重復前面的操作,在數字數到10之前退出頁面,就不會出現警告了。
import React, { Component} from 'react';
import {
View,
Text,
} from 'react-native';
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
}
}
x = 0;
count = () => {
if (this.x < 10) {
this.x++;
if (this._isMounted) {
this.setState({count: this.x});
}
setTimeout(this.count, 1000);
}
}
_isMounted;
componentWillMount() {
this.count();
}
componentWillUnmount() {
this._isMounted = false;
}
componentDidMount() {
this._isMounted = true;
}
render() {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 40}}>{this.state.count}</Text>
</View>
)
}
}
無isMounted:
有isMounted:
參考連接: