接觸react後,在項目開發過程中,發現setState的是異步方法,須要在回調函數中才能得到真正的值。javascript
this.setState({
num:this.state.num + 1
},()=>{
console.log(this.state.num);
});複製代碼
react爲何會這樣作呢?setState真的是異步的嗎?
查閱相關文章後,才得知,並不是如此。另外兩種setState同步的作法。前端
//方法一:在this.setState以後去componentDidUpdate函數中調用,此時的this.state已經更新
componentDidUpdate(){
console.log(this.state.num)
}
//方法二:在setTimeout函數中,在this.setState以後this.state是當即更新的,因此也能夠獲取到更新後的數據。
setTimeout(()=>{
this.setState({
num:this.state.num + 1
});
console.log(this.state.num);
})複製代碼
看到這裏,相信不少人都顛覆了以前對setState的異步理解,setState究竟是如何進行組件state更新的呢?
調用setState
後,會把咱們想要更新的state
壓進一個待更新隊列(即內部實例的_pendingStateQueue
),而後執行入棧更新操做enqueueUpdate
,斷定是否處於批量更新狀態。若是正在進行組件的批量更新,那麼久先把實例推動dirtyComponents
裏面等待下一次批量更新;相反若沒有批量更新在執行,則會調用一個批量更新的事務。
java
/** * 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
又爲咱們作了什麼?
react
/** * 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
就是事務的調用。
app
瞭解了setState
執行的全過程,咱們也清楚了這個函數其實並不必定是異步去執行的
,假若沒有在進行更新dom時,它仍是會當即觸發dom的更新。
dom
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
會被阻塞,直到事務徹底執行完畢後,纔會從新調用更新。ui
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(react16 已廢棄,直接使用getDerivedStateFromProps獲取props並返回新的state)
、componentDidUpdate
至此setState的原理和使用就介紹完了,可是真正使用的契機卻每每是前端開發者須要去琢磨的,對於非控制組件,這是react中必要掌握的技術基礎了