拋出問題
class Example extends Component { contructor () { super() this.state = { value: 0, index: 0 } } componentDidMount () { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第一次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第二次輸出 setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次輸出 }, 0); this.refs.button.addEventListener('click', this.click) } click = () => { this.setState({value: this.state.index + 1}) this.setState({value: this.state.index + 1}) } render () { return ( <div><span>value: {this.state.value}index: {this.props.index}</span> <button ref="button" onClick={this.click}>點擊</button> </div> ) }}這四次輸出,按常理來說分別是: 1,2,3,4。但是,實際輸出為: 0, 0, 2, 3setState的注意點
setState不會立刻改變React組件中state的值(即setState是異步更新)setState通過一個隊列機制實現state更新;當執行setState時,會將需要更新的state合併後放入狀態隊列,而不會立即更新,隊列可以高效的批量更新state;通過this.state直接修改的值,state不會放入狀態隊列,當下次調用setState並對狀態隊列進行合併時,會忽略之前直接被修改的state.setState通過引發一次組件的更新過程來引發重新繪製此處重繪指的就是引起React的更新生命周期函數4個函數:shouldComponentUpdate(被調用時this.state沒有更新;如果返回了false,生命周期被中斷,雖然不調用之後的函數了,但是state仍然會被更新)componentWillUpdate(被調用時this.state沒有更新)render(被調用時this.state得到更新)componentDidUpdate多個相鄰的state的修改可能會合併到一起一次執行this.setState({name: 'Pororo'}) this.setState({age: 20})等同於this.setState({name: 'Pororo',age: 20})上面兩塊代碼的效果是一樣的。如果每次調用都引發一次生命周期更新,那性能就會消耗很大了。所以,React會將多個this.setState產生的修改放進一個隊列裡,等差不多的時候就會引發一次生命周期更新。問題分析
對於前兩次setState:this.setState({value: this.state.val + 1});console.log(this.state.value); // 第一次輸出this.setState({value: this.state.val + 1});console.log(this.state.value); // 第二次輸出由於setState不會立即改變React組件中state的值,所以兩次setState中this.state.value都是同一個值0,故而,這兩次輸出都是0。因而value只被加1。既然這樣,那麼是不是可以直接操作this.state呢?比如:this.state.value=this.state.value+1;這樣的確可以修改this.state.value的狀態但是卻不可以引發重複渲染。所以,就必須通過React設定的setState函數去改變this.state,從而引發重新渲染。setTimeout裡面的兩次setState:setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次輸出}, 0);這兩次this.state的值同步更新了;同步更新:是由React引發的事件處理(比如:onClick引發的事件處理),調用setState會異步更新this.state;異步更新:除此之外的setState調用會同步執行this.setState。 「除此之外」指的是:繞過React通過addEventListener直接添加的事件處理函數和setTimeout/setInterval產生的異步調用。this.setState更新機製圖解:
每次setState產生新的state會依次被存入一個隊列,然後會根據isBathingUpdates變量判斷是直接更新this.state還是放進dirtyComponent裡回頭再說。isBatchingUpdates默認是false,也就表示setState會同步更新this.state。但是,當React在調用事件處理函數之前就會調用batchedUpdates,這個函數會把isBatchingUpdates修改為true,造成的後果就是由React控制的事件處理過程setState不會同步更新this.state。同步更新(函數式setState)
如果this.setState的參數不是一個對象而是一個函數時,這個函數會接收到兩個參數,第一個是當前的state值,第二個是當前的props,這個函數應該返回一個對象,這個對象代表想要對this.state的更改;換句話說,之前你想給this.setState傳遞什麼對象參數,在這種函數裡就返回什麼對象。不過,計算這個對象的方法有些改變,不再依賴於this.state,而是依賴於輸入參數state。function increment(state, props) { return {count: state.count + 1};}function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState(increment);}假如當前this.state.count的值是0,第一次調用this.setState(increment),傳給increment的state參數是0,第二調用時,state參數是1,第三次調用是,參數是2,最終incrementMultiple讓this.state.count變成了3。對於多次調用函數式setState的情況,React會保證調用每次increment時,state都已經合併了之前的狀態修改結果。要注意的是,在increment函數被調用時,this.state並沒有被改變,依然,要等到render函數被重新執行時(或者shouldComponentUpdate函數返回false之後)才被改變。
同步異步setState的用法混合
function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState({count: this.state.count + 1}); this.setState(increment);}在幾個函數式setState調用中插入一個傳統式setState調用,最後得到的結果是讓this.state.count增加了2,而不是增加4。這是因為React會依次合併所有setState產生的效果,雖然前兩個函數式setState調用產生的效果是count加2,但是中間出現一個傳統式setState調用,一下子強行把積攢的效果清空,用count加1取代。所以,傳統式setState與函數式setState一定不要混用。