接觸react
框架不久,卻在項目當中發現,非受控組件其更新時機的觸發方式——setState
,是一個異步的過程javascript
下面是一個例子:前端
handelTabChange (tabName) { this.setState({ tab: tabName }) this.updateTabPane() } updateTabPane () { const { tab } = this.state console.log( tab ) // not the latest one }
此時tab
發生變化的時候,輸出的卻依然是上一個tab
的名稱,所以能夠判斷updateTabPane
是在setState
以前執行了。
那麼,爲何setState
須要異步去改變組件的state
呢?java
React
組件是靠單向數據流構建頁面dom的,除開props
,自身的state
改變也是引發組件渲染的主要因素,爲了節省性能消耗,react
有一套本身的state
更新策略,爲的是減小state
更新對頁面渲染的消耗,而這套更新策略的入口就是setState
方法。react
這一樣也解釋了爲何直接對this.state
進行賦值操做並不能改變頁面的渲染結果。安全
那麼在異步的過程當中,setState
究竟作了哪些事兒呢?app
整體概括一下setState
更新組件的流程,調用setState
後,會把咱們想要更新的state
壓進一個待更新隊列(即內部實例的_pendingStateQueue
),而後執行入棧更新操做enqueueUpdate
,斷定是否處於批量更新狀態。若是正在進行組件的批量更新,那麼久先把實例推動dirtyComponents
裏面等待下一次批量更新;相反若沒有批量更新在執行,則會調用一個批量更新的事務。框架
/** * setState源碼 **/ ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); // 傳入新state進隊列 if (callback) { // 推入callback隊列 this.updater.enqueueCallback(this, callback, 'setState') } } enqueueSetState: function(publicInstance, partialState) { // 獲取內部實例 var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState') if (!internalInstance) { return } // 更新隊列合併操做 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []) queue.push(partialState) // 更新代碼 enqueueUpdate(internalInstance) }
setState
把咱們但願更新的partialState
推入待更新隊列以後,就撒手交給enqueueUpdate
去處理更新的時機了,咱們看一下enqueueUpdate
又爲咱們作了什麼dom
/** * enqueueUpdate源碼 **/ function enqueueUpdate(component) { ensureInjected() // 若是不處於批量更新模式 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component) return } // 若是處於批量更新模式,則將該組件保存在 dirtyComponents 中 dirtyComponents.push(component) }
能夠看到enqueueUpdate
當中出現了一個重要的對象batchingStrategy
,他有一個屬性isBatchingUpdates
用來告訴enqueueUpdate
是應該更新,仍是應該等待,把組件推入dirtyComponents
裏。能夠想象這是一個react
內部,用於控制批量更新的對象,讓咱們更近距離的瞭解它異步
/** * batchingStrategy源碼 **/ var ReactDefaultBatchingStrategy = { isBatchingUpdates: false, batchedUpdate: function(callback, a, b, c, d, e) { var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates ReactDefaultBatchingStrategy. isBatchingUpdates = true if (alreadyBatchingStrategy) { callback(a, b, c, d, e) } else { transaction.perform(callback, null, a, b, c, d, e) } } }
dirtyComponents
當中提供的batchedUpdates
其實就是咱們一直尋找的,真實用來更新咱們組件的方法。然而走到這一步,react
卻又向咱們拋出了一個重大的概念——事務。batchedUpdates
當中transaction.perform
就是事務的調用函數
瞭解了setState
執行的全過程,咱們也清楚了這個函數其實並不必定是異步去執行的,假若沒有在進行更新dom時,它仍是會當即觸發dom的更新
//this.state.val = 0 setTimeout(() => { this.setState({val: this.state}) console.log(this.state.val) // 1 this.setState({val: this.state}) console.log(this.state.val) // 2 }, 0)
當他異步的時候,從setState
的源碼中咱們也看到了,若是想要在新的state
更新後觸發操做,只須要在setState
的第二個參數當中傳入你想要執行的回調便可。
可是,若是想要解釋如下現象,咱們還須要向你們介紹事務的概念。
componentDidMount () { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 0 this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 0 }
按照剛纔的想法,當componentDidMount
執行的時候,按理說頁面上已經有完整的dom渲染結束了,爲何此時我調用setState
不能像setTimeout
裏同樣,當即執行對state
的更新呢?下面先拋出事務的簡介。
簡單來講,事務是一種react
的處理機制,經過使用wrapper
包裹你實際想要調用的方法,作一些前置(initialize
)和收尾(close
)的工做,因此在事務包裹的方法內,會優先觸發前置鉤子,以及執行完後會有收尾方法調用,這在react
用做異常處理使用。
因此此時不難想到,其實componentDidMount
是react
掛載dom節點的事務的收尾工做,在這個環節操做state
會被阻塞,直到事務徹底執行完畢後,纔會從新調用更新。
setState
會觸發組件的更新,同時在組件生命週期的鉤子函數中咱們每每會有對state
的操做,操做不當頗有可能發生state change
=》 update
=》 change state
=》 state change
……的死循環,那麼哪些鉤子函數內使用setState是安全的呢。
咱們把生命週期鉤子函數羅列出來
constructor -> componentWillMount -> render -> componentDidMount -> componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
當中constructor
中自己就有state
的聲明,在這裏是最初的state
建立,所以不須要使用setState
componentWillMount
中,若是進行同步setState
調用,此時的操做其實和constructor
內定義state
是同樣的,並不會觸發組件的額外渲染,固然這裏能夠作異步的setState
操做,獲取頁面的初始數據。
render
、shouldComponentUpdate
、componentWillUpdate
這三個函數中,組件尚未渲染結束就繼續調用setState
,會無限觸發更新,陷入死循環,是要注意的。
所以咱們可用setState
的生命週期鉤子函數有:componentWillMount
、componentDidMount
、componentWillReceiveProps
、componentDidUpdate
至此setState的原理和使用就介紹完了,可是真正使用的契機卻每每是前端開發者須要去琢磨的,對於非控制組件,這是react中必要掌握的技術基礎了