React源碼(一) setState

在入門React時,學到的一個很重要的概念就是:不要直接去修改state,而是使用setState。為什麼要這麼做呢?所以需要去了解State和setState的實現機制。

首先,我的理解是兩個方面的原因:1 做一定的限制,讓開發者不能任意的修改state,而是通過一定的途徑來處理,方便管理;2 提高更新state的效率。React在處理setState時,會維護一個更新的隊列。每次調用setState,會合併需要更新的state,加入到隊列中,等到執行更新循環時,進行批量更新state。如果你直接修改state,這個修改後的state不會進入這個隊列中,所以會跟預期的不一樣。

{ n updateComponent: function (transaction,n prevParentElement,n nextParentElement,n prevUnmaskedContext,n nextUnmaskedContext,) {n var inst = this._instance;n var nextState = this._processPendingState(nextProps, nextContext);n var shouldUpdate = true;nn if (!this._pendingForceUpdate) {n if (inst.shouldComponentUpdate) {n if (__DEV__) {n shouldUpdate = measureLifeCyclePerf(n () => inst.shouldComponentUpdate(nextProps, nextState, nextContext),n this._debugID,n shouldComponentUpdate,n );n } else {n shouldUpdate = inst.shouldComponentUpdate(n nextProps,n nextState,n nextContext,n );n }n } else {n if (this._compositeType === CompositeTypes.PureClass) {n shouldUpdate =n !shallowEqual(prevProps, nextProps) ||n !shallowEqual(inst.state, nextState);n }n }n }n n },n _processPendingState: function (props, context) {n var inst = this._instance;n var queue = this._pendingStateQueue;n var replace = this._pendingReplaceState;n this._pendingReplaceState = false;n this._pendingStateQueue = null;nn if (!queue) {n return inst.state;n }nn if (replace && queue.length === 1) {n return queue[0];n }nn var nextState = Object.assign({}, replace ? queue[0] : inst.state);n for (var i = replace ? 1 : 0; i < queue.length; i++) {n var partial = queue[i];n Object.assign(n nextState,n typeof partial === functionn ? partial.call(inst, nextState, props, context)n : partial,n );n }nn return nextState;n }n};n

上面的代碼可見於 src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

這段代碼十分有趣,可以說明很多問題。例如,React.PureComponent會檢測組件中的state和props是否發生變化,有變化才會進行更新; shouldUpdateComponent函數中返回false則不會執行組件的更新;_processPendingState方法會合併需要更新的state,然後加入到更新隊列中。

在調用setState,會執行enqueueSetState方法,然後會將partialState和_pendingStateQueue更新隊列進行合併操作,如下:

ReactComponent.prototype.setState = function(partialState, callback) {n invariant(n typeof partialState === object ||n typeof partialState === function ||n partialState == null,n setState(...): takes an object of state variables to update or a +n function which returns an object of state variables.,n );n this.updater.enqueueSetState(this, partialState);n if (callback) {n this.updater.enqueueCallback(this, callback, setState);n }n};n enqueueSetState: function(publicInstance, partialState) {n if (__DEV__) {n ReactInstrumentation.debugTool.onSetState();n warning(n partialState != null,n setState(...): You passed an undefined or null state object; +n instead, use forceUpdate().,n );n }nn var internalInstance = getInternalInstanceReadyForUpdate(n publicInstance,n setState,n );nn if (!internalInstance) {n return;n }nn var queue =n internalInstance._pendingStateQueue ||n (internalInstance._pendingStateQueue = []);n queue.push(partialState);nn enqueueUpdate(internalInstance);n }nfunction enqueueUpdate(component) {n ensureInjected();nn if (!batchingStrategy.isBatchingUpdates) {n batchingStrategy.batchedUpdates(enqueueUpdate, component);n return;n }nn dirtyComponents.push(component);n if (component._updateBatchNumber == null) {n component._updateBatchNumber = updateBatchNumber + 1;n }n}n performUpdateIfNecessary: function(transaction) {n if (this._pendingElement != null) {n ReactReconciler.receiveComponent(n this,n this._pendingElement,n transaction,n this._context,n );n } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {n this.updateComponent(n transaction,n this._currentElement,n this._currentElement,n this._context,n this._context,n );n } else {n this._updateBatchNumber = null;n }n },n

其次,還有一條定律就是,在執行組件的更新時,不要在shouldComponentUpdate和componentWillUpdate中調用setState。上面的代碼就能說明這個問題。因為在執行上面兩個方法時,如果調用setState,會調用performUpdateIfNecessay方法,該方法中會對_pendingElement, _pendingStateQueue, _pendingForceUpdate進行判斷, 而這個時候_pendingStateQueue由於會對state進行修改,所以不為空,則會調用updateComponent方法,而這個方法又會調用shouldComponentUpdate和componentWillUpdate方法,造成循環調用。

function enqueueUpdate(component) {n ensureInjected();nn // Various parts of our code (such as ReactCompositeComponentsn // _renderValidatedComponent) assume that calls to render arent nested;n // verify that thats the case. (This is called by each top-level updaten // function, like setState, forceUpdate, etc.; creation andn // destruction of top-level components is guarded in ReactMount.)nn if (!batchingStrategy.isBatchingUpdates) {n batchingStrategy.batchedUpdates(enqueueUpdate, component);n return;n }nn dirtyComponents.push(component);n if (component._updateBatchNumber == null) {n component._updateBatchNumber = updateBatchNumber + 1;n }n}nnvar ReactDefaultBatchingStrategy = {n isBatchingUpdates: false,nn /**n * Call the provided function in a context within which calls to `setState`n * and friends are batched such that components arent updated unnecessarily.n */n batchedUpdates: function(callback, a, b, c, d, e) {n var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;nn ReactDefaultBatchingStrategy.isBatchingUpdates = true;nn // The code is written this way to avoid extra allocationsn if (alreadyBatchingUpdates) {n return callback(a, b, c, d, e);n } else {n return transaction.perform(callback, null, a, b, c, d, e);n }n },n

調用setState,最終通過enqueueUpdate執行state更新,如上,其中有兩種更新的模式,一種是批量更新模式,將組建保存在dirtyComponents,另一種非批量模式,將會遍歷dirtyComponents,對每一個dirtyComponents調用updateComponent方法。批量與非批量模式,通過ReactDefaultBatchingStrategy中的isBatchingUpdates屬性來進行判斷。在非批量模式下,會立即應用新的state;而在批量模式下,需要更新state的組件會被push 到dirtyComponents,再執行更新。

說到批量處理,就一定會提到事務。我最初接觸到這個概念是在資料庫中,當開啟一個事務,可以集中進行一系列的操作,如果其中有的操作執行失敗,則會撤銷這個事務,進行回滾。在React的批量更新模式下,多個setState操作在一次事務中,方便集中管理,但是這個事務的執行過程中,開發者無法去干涉。

不過我們可以定義自己的事務,通過該事務來執行方法,同時可以做到在這個事務初始化和關閉時,去做一些初始化和清理回收的工作。下面還是來看這段經典的代碼吧

state = {count: 0}ncomponentDidMount(){n this.setState({count: this.state.count + 1});n console.log(this.state.count);nn this.setState({count: this.state.count + 1});n console.log(this.state.count);nn setTimeout(() => {n this.setState({count: this.state.count + 1});n console.log(this.state.count);nn this.setState({count: this.state.count + 1});n console.log(this.state.count);n }, 0);n}n

上面的結果分別是 0,0,3,4. 分別對應著上面所說的批量更新和非批量更新得出的結果。

參考資源:facebook/react

facebook/reactgithub.com圖標

參考資源:facebook/react

推薦閱讀:

AntV - 我認為這是一個不嚴謹的錯誤
vue-router源碼分析-整體流程
收藏指數滿格!幫你打包前端之巔一整年好文!

TAG:React | 前端开发 | 前端框架 |