在 React 文檔的 State and Lifecycle 一章中,其實有明確的說明 setState() 的用法,向 setState() 中傳入一個對象來對已有的 state 進行更新。 例如:html
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.state.count + 1
};
}
}
複製代碼
咱們若是想要對這個 state 進行更新的話,就能夠這樣使用 setState():vue
this.setState({
count: 1
});
複製代碼
可是,在 React 的文檔下面,還寫着,處理關於異步更新 state 的問題的時候,就不能簡單地傳入對象來進行更新了。這個時候,須要採用另一種方式來對 state 進行更新。react
setState() 不只可以接受一個對象做爲參數,還可以接受一個函數做爲參數。函數的參數即爲 state 的前一個狀態以及 props。git
因此,咱們能夠向下面這樣來更新 state:github
this.setState((prevState, props) => ({ count: prevState.count + 1 }));
複製代碼
爲了說明兩種方式的區別,咱們舉個比較簡單的例子。bash
咱們設置一個累加器,在 state 上設置一個 count
屬性,同時,爲其增長一個 increment
方法,經過這個 increment
方法來更新 count
。異步
此處,咱們採用給 setState()
傳入對象的方式來更新 state
,同時,咱們在此處設置每調用一次 increment
方法的時候,就調用兩次 setState()
。函數
代碼以下:ui
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
// 此處設置調用兩次 setState()
increment() {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);
複製代碼
此時點擊 button 的時候,count 就會更新了。可是,可能與咱們所預期的有所差異。咱們設置了點擊一次就調用兩次 setState(),可是,count 每一次卻仍是隻增長了 1,因此這是爲何呢?this
其實,在 React 內部,對於這種狀況,採用的是對象合併的操做,就和咱們所熟知的 Object.assign() 執行的結果同樣。
例如:
Object.assign({}, { a: 2, b: 3 }, { a: 1, c: 4 });
複製代碼
咱們最終獲得的結果將會是 { a: 1, b: 3, c: 4 }
。對象
對象合併的操做,屬性值將會以最後設置的屬性的值爲準,若是發現以前存在相同的屬性,那麼,這個屬性將會被後設置的屬性所替換。因此,也就不難理解爲何咱們調用了兩次 setState() 以後,count 依然只增長了 1 了。
本質就是下面的代碼:
this.setState({
count: this.state.count + 1
});
// 同理於
Object.assign({}, this.state, { count: this.state.count + 1 });
複製代碼
咱們將上面的累加器採用另外的方式來實現一次,在 setState() 的時候,咱們採用傳入一個函數的方式來更新咱們的 state。
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
// 此處設置調用兩次 setState()
increment() {
// 採用傳入函數的方式來更新 state
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);
複製代碼
當咱們再次點擊按鈕的時候,就會發現,咱們的累加器就會每次增長 2 了。
在 React 的源代碼中,咱們能夠看到這樣一句代碼:
this.updater.enqueueSetState(this, partialState, callback, 'setState');
複製代碼
而後,enqueueSetState
函數中又會有這樣的實現
queue.push(partialState);
enqueueUpdate(internalInstance);
複製代碼
因此,與傳入對象更新 state 的方式不一樣,咱們傳入函數來更新 state 的時候,React 會把咱們更新 state 的函數加入到一個隊列裏面,而後,按照函數的順序依次調用。同時,爲每一個函數傳入 state 的前一個狀態,這樣,就能更合理的來更新咱們的 state 了。
咱們在處理異步更新的時候,須要用到傳入函數的方式來更新咱們的 state。這樣,在更新下一個 state 的時候,咱們可以正確的獲取到以前的 state,並在在其基礎之上進行相應的修改。而不是簡單地執行所謂的對象合併。
另外最近正在寫一個編譯 Vue 代碼到 React 代碼的轉換器,歡迎你們查閱。