一、說明
關於調用 setState()
進行狀態更新,官方有如下說明
State Updates May Be Asynchronous React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. 譯: State 的更新可能是異步的 出於性能方面的考慮,React 可以將多次的 setState() 調用合並為一次 因為 this.props 和 this.state 可能是異步更新的,你不應該用它們當前的值去計算下一個 state 的值
在 React 的 setState 函數實現中,會根據一個變量 isBatchingUpdates 判斷是直接更新 this.state 還是放到隊列中延時更新,而 isBatchingUpdates 默認是 false,表示 setState 會同步更新 this.state;但是,有一個函數 batchedUpdates,該函數會把 isBatchingUpdates 修改為 true,而當 React 在調用事件處理函數之前就會先調用這個 batchedUpdates將isBatchingUpdates修改為true,這樣由 React 控制的事件處理過程 setState 不會同步更新 this.state。
二、setState何時同步何時異步?
由React控制的事件處理程序,以及生命周期函數調用setState不會同步更新state 。
React控制之外的事件中調用setState是同步更新的。比如原生js綁定的事件,setTimeout/setInterval等。
大部分開發中用到的都是React封裝的事件,比如onChange、onClick、onTouchMove等,這些事件處理程序中的setState都是異步處理的。
三、同步更新策略
1.完成回調
setState函數的第二個參數允許傳入回調函數,在狀態更新完畢后進行調用,譬如:
this.setState({ load: !this.state.load, count: this.state.count + 1 }, () => { console.log(this.state.count); console.log('加載完成') });
2.Promise封裝結合async/await
其實這里就是JavaScript異步編程相關知識,將上面回調寫法換成Promise
引入Promise來封裝setState:
setStateAsync(state) { return new Promise((resolve) => { this.setState(state, resolve) }); }
setStateAsync 返回的是Promise對象,在調用時我們可以使用Async/Await語法來優化代碼風格:
async componentDidMount() {
}
3.直接使用操作異步函數async / await
此時每次await setState都會執行一遍render方法
handleChange = async () => {
}
四、setState參數詳解
state中定義count初始值為1;
1.第一個參數為傳入對象;
handleChange = () => { this.setState({ count: this.state.count + 2 }) console.log(123, this.state.count) this.setState({ count: this.state.count + 2 }) console.log(456, this.state.count) }
調用方法兩次輸出值為都為3;
React 出於性能方面的考慮,並不會直接對每次的調用都進行更新,而是會將多次傳入的對象進行合並處理,以產生一個新的最終的 state
對象;
第二個參數為完成回調
handleChange = () => { this.setState({ count: this.state.count + 2 }, () => { console.log(123, this.state.count) }) this.setState({ count: this.state.count + 2 }, () => { console.log(456, this.state.count) }) }
與上面輸出保持一致
2.第一個參數為傳入函數:
handleChange = () => { this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(123, this.state.count) }) this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(456, this.state.count) }) }
函數第一個參數接收先前的狀態作為第一個參數,將此次更新被應用時的props做為第二個參數;
此時輸出為兩次都是5;
在第二次調用 setState 方法時便可以通過 prevState.count 拿到最新的值從而更新本次的 state 。顯然,React 對於傳入函數的方式和傳入對象的方式進行更新 state 的各自具體理念是不一樣的,對於傳入函數的方式,在調用 setState 進行更新 state 時,React 會按照各個 setState 的調用順序,將它們依次放入一個隊列,然后,在進行狀態更新時,則按照隊列中的先后順序依次調用,並將上一個調用結束時產生的 state 傳入到下一個調用的函數中,當然,第一個 setState 調用時,傳入的 prevState 則是當前的 state ,如此,便解決了傳入對象式調用 setState 方法所存在的 不能依賴上一次的 state 去計算本次 state 的問題。
handleChange = () => { this.setState((prevState, props) => ({ count: prevState.count + 2 }), () => { console.log(123, this.state.count) }) this.setState((prevState, props) => ({ count: this.state.count + 2 }), () => { console.log(456, this.state.count) }) }
值得一提的是,在上面的這段代碼中,執行第二個 setState 里面的函數時,由第一個 setState 所產生的最新的 state 並沒有合並到 this 對象上面去,所以此時通過 this.state 獲取不到最新的狀態,故而在函數內部拿到的 this.state.count 的值為 1 而非 3。
另外,不論第一個參數是何種類型,多次setState也只會調用一次render方法,第二個參數callback完成回調中獲取this.state的值同樣也是多次setState合並操作后的最終值;