研究 setState
這個問題來源於一個疑惑:使用 redux 的時候 dispatch
一個 action
,爲何能夠致使視圖的更新?react
首先的猜測是 store
改變後,redux 在某處調用了 setState
,通知了 react。redux
看了下代碼發現確實如此,調用 dispatch action
會觸發一個 onStateChange
的函數 (這個函數在 connect
的時候就被註冊到 store
了, store
被 reducer
修改後觸發),onStateChange
函數判斷若是須要 shouldComponentUpdate
的話則執行 this.setState({})
來觸發 react 更新。數組
那麼問題來了:瀏覽器
setState
可讓視圖更新,它是如何一步步到 virtualDOM
而後渲染的呢setState
爲何有時表現是異步的有時又是同步的?willReceiveProps
裏能夠setState
而willUpdate
不行?捋了一下流程得出下圖,圖中每一個流程塊冒號前即爲被執行的函數:緩存
簡要的說一下流程:bash
setState
後將傳入的 state
放入隊列 queue
,enqueueUpdate
方法會根據 isBatchingUpdate
標誌位判斷,若當前已經在更新組件則將直接當前組件放入 dirtyComponents
數組,不然將 isBatchingUpdate
置爲 true 並開啓一個 "批量更新 (batchedUpdates
)" 的事務(transaction
)。簡單地說,一個所謂的
Transaction
就是將須要執行的method
使用wrapper
封裝起來,再經過Transaction
提供的perform
方法執行。而在perform
以前,先執行全部wrapper
中的initialize
方法;perform
完成以後(即method
執行後)再執行全部的close
方法。一組initialize
及close
方法稱爲一個wrapper,
Transaction
支持多個wrapper
疊加。app
事務開啓後會依次執行 initialize、perform、close
方法。能夠看到,batchedUpdates
在 perform
階段會再次執行 enqueueUpdate
方法,因爲這時的 isBatchingUpdate
已是 true 了因此會將當前組件放入 dirtyComponents
。關鍵就在 close
階段了,若是 dirtyComponents
爲空則表示不須要更新,不然就開始更新,開啓 flushBatchedUpdates
事務。異步
flushBatchedUpdates
在 perform
階段會將 dirtyComponents
中的組件按 父 > 子
組件的順序調用更新方法,組件在更新的時候會依次執行:willReceiveProps -> 將 queue 中緩存的 state 與緩存的 state 合併 -> shouldComponentUpdate。
複製代碼
若是判斷須要更新,則執行組件的 render
方法獲得新的 reactElement
,將其與以前的 reactElement
作 diff 便可,將 diff 結果(刪除,移動等)經過 setInnerHTML
等封裝方法更新視圖便可,細節可見圖。函數
flushBatchedUpdates
在 close
階段會再次檢查 dirtyComponents
長度有沒有變化,若是變化了說明存在有新的 dirtyComponent
,須要再來一次 flushBatchedUpdates
。補上 updateComponent
代碼:ui
// 更新組件
updateComponent: function(transaction, prevParentElement, nextParentElement) {
var prevContext = this.context;
var prevProps = this.props;
var nextContext = prevContext;
var nextProps = prevProps;
if (prevParentElement !== nextParentElement) {
nextContext = this._processContext(nextParentElement._context);
nextProps = this._processProps(nextParentElement.props);
// 當前狀態爲 RECEIVING_PROPS
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
// 若是存在 componentWillReceiveProps,則執行
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, nextContext);
}
}
// 設置狀態爲 null,更新 state
this._compositeLifeCycleState = null;
var nextState = this._pendingState || this.state;
this._pendingState = null;
var shouldUpdate =
this._pendingForceUpdate ||
!this.shouldComponentUpdate ||
this.shouldComponentUpdate(nextProps, nextState, nextContext);
if (!shouldUpdate) {
// 若是肯定組件不更新,仍然要設置 props 和 state
this._currentElement = nextParentElement;
this.props = nextProps;
this.state = nextState;
this.context = nextContext;
this._owner = nextParentElement._owner;
return;
}
this._pendingForceUpdate = false;
......
// 若是存在 componentWillUpdate,則觸發
if (this.componentWillUpdate) {
this.componentWillUpdate(nextProps, nextState, nextContext);
}
// render 遞歸渲染
var nextMarkup = this._renderedComponent.mountComponent(
thisID,
transaction,
this._mountDepth + 1
);
// 若是存在 componentDidUpdate,則觸發
if (this.componentDidUpdate) {
transaction.getReactMountReady().enqueue(
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
this
);
}
},
複製代碼
捋完整個流程能夠回答以前一些疑惑:
setState
後緊接着打 log,有時 state
沒有馬上變,有時候又變了?生命週期中的 setState
處於一個大的 transaction
中,此時的 isBatchingUpdate
爲 true
,執行 setState
只會讓 dirtyComponents
數組 push 當前組件而不會進一步處理,此時 log 來看的話 state
仍是沒有變的。而若是在 transaction 以外,例如 setTimeout
裏 setState
,此時 isBatchingUpdate
爲 false
,會一路直接執行下來更改 state
,因此此時 log 出來 state
是被馬上改變了的。所以 setState 不保證是同步,而不是說它必定是異步。
2. 都在同一個 tranaction
中,爲何在 willReceiveProps
時還能夠 setState
,而在 shouldComponentUpdate
和 willUpdate
的時候 setState
會致使瀏覽器死循環?
組件內部有一標誌位 _compositeLifeCycleState
表示當前生命週期狀態,在 willReceiveProps
前被設置爲 RECEIVING_PROPS
,在 willReceiveProps
執行後被設置爲 null,而 performUpdateIfNecessary
函數在當前狀態爲 MOUNTING
或 RECEIVING_PROPS
時不會繼續調用 updateComponent
函數。
performUpdateIfNecessary: function(transaction) {
var compositeLifeCycleState = this._compositeLifeCycleState;
// ■■■■■■■■重點■■■■■■■■■■■■
// 當狀態爲 MOUNTING 或 RECEIVING_PROPS 時,則不更新
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
return;
}
var prevElement = this._currentElement;
var nextElement = prevElement;
if (this._pendingElement != null) {
nextElement = this._pendingElement;
this._pendingElement = null;
}
// 調用 updateComponent
this.updateComponent(
transaction,
prevElement,
nextElement
);
}
複製代碼
所以在 willReceiveProps
時 setState
因爲 _compositeLifeCycleState
已是 RECEIVING_PROPS
了,不回觸發新的 updateComponent
,而在 willUpdate
的時候 _compositeLifeCycleState
已經被置回 null 了,所以會引起下一次的 updateComponent
,而後就再次觸發組件的各生命週期,固然也會免不了執行 willUpdate
,所以進入了死循環。