在 React 平常的使用中,一個很重要的點就是,不要直接去修改 state。例如:this.state.count = 1
是沒法觸發 React 去更新視圖的。由於React的機制規定,一個state的更新,首先須要調用 setState 方法。javascript
this.setState({ count: 1 })
這樣便能觸發從新渲染。稍有經驗的開發者會知道,setState 方法實際上是 「異步」 的。即立馬執行以後,是沒法直接獲取到最新的 state 的,須要通過 React 對 state 的全部改變進行合併處理以後,纔會去計算新的虛擬dom,再根據最新的虛擬dom去從新渲染真實dom。java
class App extends Component { state = { count: 0 } componentDidMount(){ this.setState({count: this.state.count + 1}) console.log(this.state.count) // 0 } render(){ ... } }
demo請點擊react
那怎麼才能獲取到修改後的state呢?React爲咱們提供了一個回調去實現。git
... this.setState({count: this.state.count + 1}, ()=>{ console.log(this.state.count) // 1 }) ...
回調裏的 state 即是最新的了,緣由是該回調的執行時機在於state合併處理以後。若是咱們這樣去作:github
... this.setState({count: this.state.count + 1}) this.setState({count: this.state.count + 1}) ...
實際最終的 count 會等於 1,緣由是執行時獲得的 this.state.count = 0
。那怎麼實現結果爲 2 呢?segmentfault
... this.setState(prevState => {count: prevState.count + 1}); this.setState(prevState => {count: prevState.count + 1}); ...
setState()
實際上能夠接受一個函數做爲參數,函數的首個參數就是上一次的state。後端
以上介紹了setState
的三種使用方式,下面咱們來看看它們的執行時機是怎樣的:性能優化
... this.setState({ count: this.state.count + 1 }); console.log("console: " + this.state.count); // 0 this.setState({ count: this.state.count + 1 }, () => { console.log("console from callback: " + this.state.count); // 2 }); this.setState(prevState => { console.log("console from func: " + prevState.count); // 1 return { count: prevState.count + 1 }; }, ()=>{ console.log('last console: '+ this.state.count) }); ...
執行結果:微信
console: 0 console from func: 1 console from callback: 2 last console: 2
React 其實會維護着一個 state 的更新隊列,每次調用 setState 都會先把當前修改的 state 推動這個隊列,在最後,React 會對這個隊列進行合併處理,而後去執行回調。根據最終的合併結果再去走下面的流程(更新虛擬dom,觸發渲染)。dom
由於setState()
以後沒法立馬獲取最新的 state,給人的感受即是異步去設置狀態。也確實是有異步的感受(實 際原理後面講訴)。那麼爲何 React 要把狀態的更新設計成這種方式呢?直接 this.state.count = 1
很差嗎?
有興趣的能夠點擊看看:https://github.com/facebook/react/issues/11527#issuecomment-360199710
這邊簡單總結下:
state
是同步更新,props
也不是。(你只有在父組件從新渲染時才能知道props
)state
的更新延緩到最後批量合併再去渲染對於應用的性能優化是有極大好處的,若是每次的狀態改變都去從新渲染真實dom,那麼它將帶來巨大的性能消耗。咱們先來看一段代碼,執行前建議你們先預估下結果:
class App extends Component { state = { count: 0 }; componentDidMount() { // 生命週期中調用 this.setState({ count: this.state.count + 1 }); console.log("lifecycle: " + this.state.count); setTimeout(() => { // setTimeout中調用 this.setState({ count: this.state.count + 1 }); console.log("setTimeout: " + this.state.count); }, 0); document.getElementById("div2").addEventListener("click", this.increment2); } increment = () => { // 合成事件中調用 this.setState({ count: this.state.count + 1 }); console.log("react event: " + this.state.count); }; increment2 = () => { // 原生事件中調用 this.setState({ count: this.state.count + 1 }); console.log("dom event: " + this.state.count); }; render() { return ( <div className="App"> <h2>couont: {this.state.count}</h2> <div id="div1" onClick={this.increment}> click me and count+1 </div> <div id="div2">click me and count+1</div> </div> ); } }
探討前,咱們先簡單瞭解下react的事件機制:react爲了解決跨平臺,兼容性問題,本身封裝了一套事件機制,代理了原生的事件,像在jsx
中常見的onClick
、onChange
這些都是合成事件。
那麼以上4種方式調用setState()
,後面緊接着去取最新的state,按以前講的異步原理,應該是取不到的。然而,setTimeout
中調用以及原生事件中調用的話,是能夠立馬獲取到最新的state的。根本緣由在於,setState並非真正意義上的異步操做,它只是模擬了異步的行爲。React中會去維護一個標識(isBatchingUpdates
),判斷是直接更新仍是先暫存state進隊列。setTimeout
以及原生事件都會直接去更新state,所以能夠當即獲得最新state。而合成事件和React生命週期函數中,是受React控制的,其會將isBatchingUpdates
設置爲 true
,從而走的是相似異步的那一套。
此處總結是直接引用了:http://www.javashuo.com/article/p-yozjqosa-gs.html
setState
只在合成事件和鉤子函數中是「異步」的,在原生事件和 setTimeout
中都是同步的。setState
的「異步」並非說內部由異步代碼實現,其實自己執行的過程和代碼都是同步的,只是合成事件和鉤子函數的調用順序在更新以前,致使在合成事件和鉤子函數中無法立馬拿到更新後的值,形式了所謂的「異步」,固然能夠經過第二個參數 setState(partialState, callback) 中的callback拿到更新後的結果。setState
的批量更新優化也是創建在「異步」(合成事件、鉤子函數)之上的,在原生事件和setTimeout 中不會批量更新,在「異步」中若是對同一個值進行屢次 setState
, setState
的批量更新策略會對其進行覆蓋,取最後一次的執行,若是是同時 setState
多個不一樣的值,在更新時會對其進行合併批量更新。參考:
坦白說,本身的水平仍是挺有限的,還須要努力努力再努力。只有懂了不少,才能寫出有意義的東西吧。如今寫的,大多都是根據別人的文章,本身理解了以後,換成本身的語言再表達一遍而已。。
慢慢來,總有一天能夠突破這個界限的,真正寫出屬於本身的東西。
另外,真心感謝這些文章的做者給予的幫助。
微信號:rcgrcg,歡迎交友~
爲了生計,我也接外包項目的哦~
網站搭建(PC、H五、先後端全包,咱們有團隊的哦),APP開發(安卓和IOS),都是有成功案例的哦。
有興趣的請聯繫我!!服務包您滿意的那種!!
Good luck!2018-11-18 廈門