this.setState( )方法是React.js中最多見的一種方法,利用它能夠控制各類狀態變化,達到頁面各類交互效果,可是,咱們在React開發中偶爾會發現,明明已經經過this.setState( )方法處理過某個state的值,可是在後續的方法裏,log打印出來仍然是以前的值,或者,第一次獲取到原來的值,第二次才能獲取到設置以後的新值,讓人誤覺得是由於電腦或瀏覽器性能問題形成的"延遲"問題。react
爲了理解這個問題,咱們首先來看一下setState這個過程當中發生了什麼:瀏覽器
setState
傳入的partialState
參數存儲在當前組件實例的state
暫存隊列中。componentWillReceiveProps
。state
暫存隊列中的state
進行合併,得到最終要更新的state
對象,並將隊列置爲空。componentShouldUpdate
,根據返回值判斷是否要繼續更新。componentWillUpdate
。render
從新渲染。componentDidUpdate
。首先思考爲何會出現這種狀況,在facebook給出的官方文檔中咱們能夠看到這麼一段話:性能優化
setState(updater[, callback])
Think of setState( ) as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.app
setState( ) does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState( ) a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.dom
總結如下,就是如下幾點:異步
舉個簡單例子:函數
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }); console.log(this.state.num); // 1 }
這是由於this.setState( )自己是異步的,程序異步運行,能夠提升程序運行的效率,沒必要等一個程序跑完,再跑下一個程序,特別當這兩個程序是無關的時候。React會去合併全部的state變化,在前一個方法未執行完時,就先開始運行後一個方法。可是實際操做中,爲了能實時獲取後一個狀態值,須要一些解決的辦法。性能
嘗試一下換個寫法,利用全局屬性的辦法而不是用state的方式去獲取數據:優化
constructor(props) { super(props); this.num = 1; } componentDidMount = () => { this.num = this.num + 1; console.log(this.num); // 2 }
這實際上是一種取巧的方式,寫法方便,原理簡單,可是並不十分推薦,由於它並不符合React中關於有狀態組件的設計理念,存在有可能沒法觸發刷新的風險(雖然在個人開發過程從沒有發生這樣的事),因此仍是但願你們優先使用下面的方法。this
回調函數衆所周知,就是某個函數執行完畢後執行的函數,利用它能夠確保在this.setState( )整個函數執行完成以後去獲取this.state.xxx的值:
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }, () => { console.log(this.state.num); // 2 }); console.log(this.state.num); // 1 }
控制檯按順序前後打印出兩個結果:
1 2
首先簡單回顧一下,利用setTimeout( )模擬一下前文提到的Javascript中的異步:
foo = () => { console.log('11111111'); setTimeout(function(){ console.log('22222222'); },1000); }; bar = () => { console.log('33333333'); } foo(); bar(); // 11111111 // 33333333 // 22222222
因此,在上述代碼塊中,在前一方法(foo)執行時,後一方法(bar)也能夠執行。符合異步的基本概念,程序並不按順序執行。在foo函數中執行到setTimeout的時候,函數會跳出,並先執行bar( )方法,這樣就模擬了一個異步的效果。這裏順便再提一下前面說的,setState方法經過一個隊列機制實現state更新,當執行setState的時候,會將須要更新的state合併以後放入狀態隊列,而不會當即更新,經過下面的例子可見。
constructor(props) { super(props); this.state = { num: 1, }; } componentWillMount = () => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); this.setState({ num: this.state.num + 1, }); console.log(this.state.num); } render() { console.log(this.state.num); return (<div />); }
代碼輸出結果爲 1,1,2
利用setTimeout方法能夠解決state的異步問題,由於setState只在合成事件和鉤子函數中是「異步」的,在原生事件和setTimeout 中都是同步的:
componentWillMount = () => { setTimeout(() => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 1 this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 2 }, 0); }
根據前面文檔所說,在componentDidUpdate( )方法中去獲取新的state值,根據React的生命週期,此時this.state已經更新。
constructor(props) { super(props); this.state = { num: 1 }; } componentWillMount = () => { this.setState({ num: this.state.num + 1 }); } componentDidUpdate = () => { console.log(this.state.num); // 2 }
⚠️注意,不少新人在遇到這種問題時無所適從,可能會用一些投機取巧的方式,方面的全局對象是一種方式,還有一種就是繞過setState直接賦值:
this.state.num = 2 // 2
理論上講,這種方法固然也能達到賦值目的,但將state設計成更新延緩到最後批量合併再去渲染,對於應用的性能優化是有極大好處的,若是每次的狀態改變都去從新渲染真實dom,那麼它將帶來巨大的性能消耗,因此不建議上面寫法。
⚠️若是在shouldComponentUpdate或者componentWillUpdate方法中調用setState,此時this._pending-StateQueue != null,就會形成循環調用,使得瀏覽器內存佔滿後崩潰。