在react開發過程中,state是組件一個重要的屬性,對state的管理也尤爲重要,這裏記錄一下踩坑經歷
修改state正確方式
對於修改組件state正確的方式如下:
// 錯誤
this.state.title = 'React';
// 正確
this.setState({title: 'React'});
有兩個比較重要的點,一是對state的修改是異步,二是對多個相鄰的state的修改可能會合併到一起一次執行。代碼舉例:
異步執行
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { num: 1 };
}
handleClick() {
this.setState((prevState, props) => ({
num: prevState.num + 1
}));
console.info(this.state.num);
}
render() {
return (
<div>
<h1 onClick={this.handleClick.bind(this)}>Hello, world!</h1>
<h2>It is {this.state.num}.</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
結果在ui顯示每一次加1之後的結果,而console打印的總是上一次ui的值,也就可以確定state並沒有立即執行,而是異步去執行的。我們可以通過添加一個回調函數到第二個參數,讓state更新完成之後來執行該回調,代碼如下:
handleClick() {
this.setState({
num : this.state.num + 1
});
}
此時ui上顯示的數值與console打印的會是一致。另一種情況,如果對state的修改依賴state和prop,那麼由於異步的原因就會產生很多異常情況,我們可以可以傳入包含兩個參數的函數到第一個參數來解決,代碼如下:
handleClick() {
this.setState((prevState, props) => ({
num: prevState.num + 1
}),() => console.info(this.state.num));
}
第一個參數prevState表示前一次的state,後一個參數表示當前狀態組件的prop值,顯示的讓該操作同步執行
批量單次執行
對於多次相鄰的state修改操作的執行會被合併在一起執行,代碼如下:
handleClick() {
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
}
最終數值每次只會加1,我們可以看作是以下代碼的執行:
Object.assign(
previousState,
{num : this.state.num + 1},
{num : this.state.num + 1}
)
因此,被合併之後最終只會保留一個更新。解決方法和上述解決異步的方式類似:
handleClick() {
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
}
結果是我們期望的每次加3,但是需要注意的是,console打印的只會是每次加3之後的數值,並不會是每次加1的結果
State與Immutable
React官方建議把State當作是不可變對象,一方面是如果直接修改this.state,組件並不會重新render;另一方面State中包含的所有狀態都應該是不可變對象。當State中的某個狀態發生變化,我們應該重新創建這個狀態對象,而不是直接修改原來的狀態。那麼,當狀態發生變化時,如何創建新的狀態呢?根據狀態的類型,可以分成三種情況:
狀態的類型是不可變類型(數字,字符串,布爾值,null, undefined)
這種情況最簡單,因爲狀態是不可變類型,直接給要修改的狀態賦一個新值即可。如要修改count(數字類型)、title(字符串類型)、success(布爾類型)三個狀態:this.setState({ count: 1, title: 'Redux', success: true })
狀態的類型是數組
如有一個數組類型的狀態books,當向books中增加一本書時,使用數組的concat方法或ES6的數組擴展語法(spread syntax):// 方法一:將state先賦值給另外的變量,然後使用concat創建新數組 var books = this.state.books; this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat創建新數組 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; }))
當從books中截取部分元素作爲新狀態時,使用數組的slice方法:
// 方法一:將state先賦值給另外的變量,然後使用slice創建新數組 var books = this.state.books; this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice創建新數組 this.setState(preState => ({ books: preState.books.slice(1,3); }))
注意不要使用push、pop、shift、unshift、splice等方法修改數組類型的狀態,因爲這些方法都是在原數組的基礎上修改,而concat、slice、filter會返回一個新的數組。
狀態的類型是普通對象(不包含字符串、數組)
3.1 使用ES6 的Object.assgin方法// 方法一:將state先賦值給另外的變量,然後使用Object.assign創建新對象 var owner = this.state.owner; this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign創建新對象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); }))
3.2 使用對象擴展語法
// 方法一:將state先賦值給另外的變量,然後使用對象擴展語法創建新對象 var owner = this.state.owner; this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、對象擴展語法創建新對象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; }))
參考:
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/state-and-lifecycle.html