
api解析: setState(updater, [callback])
updater: 更新數(shù)據(jù) FUNCTION/OBJECT
callback: 更新成功后的回調(diào) FUNCTION
// updater - Function
this.setState((prevState, props) => {
return {counter: prevState.counter + props.step};
});
// update - Object
this.setState({quantity: 2})
setState的特點:
1.異步:react通常會集齊一批需要更新的組件,然后一次性更新來保證渲染的性能
2.淺合并 Objecr.assign()
setState問題與解決
舉個??
- 在使用setState改變狀態(tài)之后,立刻通過this.state去拿最新的狀態(tài)
解決: componentDidUpdate或者setState的回調(diào)函數(shù)里獲取
// setState回調(diào)函數(shù)
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
- 有一個需求,需要在在onClick里累加兩次,使用對象的方法更新,則只會加一次
解決: 使用updater function
onClick = () => {
this.setState({ index: this.state.index + 1 });
this.setState({ index: this.state.index + 1 });
}
// 最后解析為,后面的數(shù)據(jù)會覆蓋前面的更改,所以最終只加了一次.
Object.assign(
previousState,
{index: state.index+ 1},
{index: state.index+ 1},
)
//正確寫法
onClick = () => {
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
}
注意:
1.不要在render()函數(shù)里面寫setstate(),除非你自己定制了shouldComponentUpdate方法,要不然會引起無限循環(huán)
render() {
//this.setState
return(
//...dom
)
}
2.不要給this.state直接復制
react為了實現(xiàn)高效render, state其實是一個隊列,setState是將數(shù)據(jù)插入隊列中,使用方式1直接賦值不會觸發(fā)渲染, react提供了setState的實例方法可以觸發(fā)render。
// 1
this.state.num = 1
// 2
this.setState({
num: this.state.num + 1
})
3.對數(shù)組和對象等引用對象操作時,使用返回新對象的方法
array: 不要使用push、pop、shift、unshift、splice可使用concat、slice、filter、擴展語法
object: Object.assgin/擴展語法
setState更新機制
如圖:

圖不清楚可以點擊查看原圖
-
partialState:setState傳入的第一個參數(shù),對象或函數(shù) -
_pendingStateQueue:當前組件等待執(zhí)行更新的state隊列 -
isBatchingUpdates:react用于標識當前是否處于批量更新狀態(tài),所有組件公用 -
dirtyComponent:當前所有處于待更新狀態(tài)的組件隊列 -
transcation:react的事務機制,在被事務調(diào)用的方法外包裝n個waper對象,并一次執(zhí)行:waper.init、被調(diào)用方法、waper.close -
FLUSH_BATCHED_UPDATES:用于執(zhí)行更新的waper,只有一個close方法
2.執(zhí)行過程
對照上面流程圖的文字說明,大概可分為以下幾步:
- 1.將setState傳入的
partialState參數(shù)存儲在當前組件實例的_pendingStateQueue中。 - 2.判斷當前React是否處于批量更新狀態(tài),如果是,將當前組件標記為dirtyCompontent,并加入待更新的組件隊列中。
- 3.如果未處于批量更新狀態(tài),將
isBatchingUpdates設置為true,用事務再次調(diào)用前一步方法,保證當前組件加入到了待更新組件隊列中。 - 4.調(diào)用事務的
waper方法,遍歷待更新組件隊列依次執(zhí)行更新。 - 5.執(zhí)行生命周期
componentWillReceiveProps。 - 6.將組件的state暫存隊列中的
state進行合并,獲得最終要更新的state對象,并將_pendingStateQueue置為空。 - 7.執(zhí)行生命周期
shouldComponentUpdate,根據(jù)返回值判斷是否要繼續(xù)更新。 - 8.執(zhí)行生命周期
componentWillUpdate。 - 9.執(zhí)行真正的更新,
render。 - 10.執(zhí)行生命周期
componentDidUpdate。
setState源碼世界
相信能到這里的同學都知道了setState()是個既能同步又能異步的方法了,那具體什么時候是同步的,什么時候是異步的?
去源碼里面看實現(xiàn)是比較靠譜的方式。
1、如何快速查看react源碼
上react的github倉庫,直接clone下來
git clone https://github.com/facebook/react.git
到目前我看為止,最新的版本是16.13.1,我選了15.6.0的代碼
如何切換版本?
1、找到對應版本號

2、復制15.6.0的歷史記錄號

3、回滾
git reset --hard 911603b
如圖,成功回滾到15.6.0版本

2、setState入口 => enqueueSetState
核心原則:既然是看源碼,那當然就不是一行一行的讀代碼,而是看核心的思想,所以接下來的代碼都只會放核心代碼,旁枝末節(jié)只提一下或者忽略setState的入口文件在src/isomorphic/modern/class/ReactBaseClasses.jsReact組件繼承自React.Component,而setState是React.Component的方法,因此對于組件來講setState屬于其原型方法
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
partialState顧名思義-“部分state”,這取名,大概就是想不影響原來的state的意思吧
當調(diào)用setState時實際上是調(diào)用了enqueueSetState方法,我們順藤摸瓜(我用的是vscode的全局搜索),找到了這個文件src/renderers/shared/stack/reconciler/ReactUpdateQueue.js

這個文件導出了一個ReactUpdateQueue對象,“react更新隊列”,代碼名字起的好可以自帶注釋,說的就是這種大作吧,在這里注冊了enqueueSetState方法
3、enqueueSetState => enqueueUpdate
先看enqueueSetState的定義
enqueueSetState: function(publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState',
);
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
這里只需要關注internalInstance的兩個屬性:
- _pendingStateQueue:待更新隊列
- _pendingCallbacks: 更新回調(diào)隊列
如果_pendingStateQueue的值為null,將其賦值為空數(shù)組[],并將partialState放入待更新state隊列_pendingStateQueue,最后執(zhí)行enqueueUpdate(internalInstance)
接下來看enqueueUpdatefunction
enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
它執(zhí)行的是ReactUpdates的enqueueUpdate方法
var ReactUpdates = require('ReactUpdates');
這個文件剛好就在旁邊src/renderers/shared/stack/reconciler/ReactUpdates.js。找到enqueueUpdate方法
var ReactUpdates = {
/**
* React references `ReactReconcileTransaction` using this property in order
* to allow dependency injection.
*
* @internal
*/
ReactReconcileTransaction: null,
batchedUpdates: batchedUpdates,
enqueueUpdate: enqueueUpdate,
flushBatchedUpdates: flushBatchedUpdates,
injection: ReactUpdatesInjection,
asap: asap,
};
module.exports = ReactUpdates;
定義如下
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
這段代碼對于理解setState非常重要
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
判斷batchingStrategy.isBatchingUpdates。batchingStrategy是批量更新策略,isBatchingUpdates表示是否處于批量更新過程,開始默認值為false
上面這句話的意思是:
如果處于批量更新模式,也就是isBatchingUpdates為true時,不進行state的更新操作,而是將需要更新的component添加到dirtyComponents數(shù)組中;如果不處于批量更新模式,對所有隊列中的更新執(zhí)行batchedUpdates方法,往下看下去就知道是用事務的方式批量的進行component的更新,事務在下面。
借用《深入React技術?!稰age167中一圖

4、核心:batchedUpdates => 調(diào)用transaction
那batchingStrategy.isBatchingUpdates又是怎么回事呢?看來它才是關鍵.
但是,batchingStrategy 對象并不好找,它是通過 injection 方法注入的,一番尋找,發(fā)現(xiàn)了 batchingStrategy 就是ReactDefaultBatchingStrategy。 src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js具體怎么找文件,又屬于另一個范疇了,我們今天只專注 setState,其他的容后再說吧
相信部分同學看到這里已經(jīng)有些迷糊了,沒關系,再堅持一下,旁枝末節(jié)先不管,只知道我們找到了核心方法batchedUpdates,馬上要勝利了,別放棄(我第一次看也是這樣熬過來的,一遍不行就兩遍,大不了看多幾遍又如何)
先看批量更新策略-batchingStrategy,它到底是什么
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
module.exports = ReactDefaultBatchingStrategy;
終于找到了,isBatchingUpdates屬性和batchedUpdates方法如果isBatchingUpdates為true,當前正處于更新事務狀態(tài)中,則將Component存入dirtyComponent中,否則調(diào)用batchedUpdates處理,發(fā)起一個transaction.perform()
注:所有的 batchUpdate 功能都是通過執(zhí)行各種 transaction 實現(xiàn)的這是事務的概念,先了解一下事務吧
5、Transaction(事務)
這一段就直接引用書本里面的概念吧,《深入React技術?!稰age169

簡單地說,一個所謂的 Transaction 就是將需要執(zhí)行的 method 使用 wrapper 封裝起來,再通過 Transaction 提供的 perform 方法執(zhí)行。而在 perform 之前,先執(zhí)行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 執(zhí)行后)再執(zhí)行所有的 close 方法。一組 initialize 及 close 方法稱為一個 wrapper,從上面的示例圖中可以看出 Transaction 支持多個 wrapper 疊加。
具體到實現(xiàn)上,React 中的 Transaction 提供了一個 Mixin 方便其它模塊實現(xiàn)自己需要的事務。而要使用 Transaction 的模塊,除了需要把 Transaction 的 Mixin 混入自己的事務實現(xiàn)中外,還需要額外實現(xiàn)一個抽象的 getTransactionWrappers 接口。這個接口是 Transaction 用來獲取所有需要封裝的前置方法(initialize)和收尾方法(close)的,因此它需要返回一個數(shù)組的對象,每個對象分別有 key 為 initialize 和 close 的方法。
下面這段代碼應該能幫助理解
var Transaction = require('./Transaction');
// 我們自己定義的 Transaction
var MyTransaction = function() {
// do sth.
this.reinitializeTransaction();
};
Object.assign(MyTransaction.prototype, Transaction.Mixin, {
getTransactionWrappers: function() {
return [{
initialize: function() {
console.log('before method perform');
},
close: function() {
console.log('after method perform');
}
}];
};
});
var transaction = new MyTransaction();
var testMethod = function() {
console.log('test');
}
transaction.perform(testMethod);
// before method perform
// test
// after method perform
看了上面的代碼,如果還沒有了解transaction.。沒關系??梢钥匆幌逻@篇文章,寫的非常詳細
React transaction完全解讀
6、核心分析:batchingStrategy 批量更新策略
回到batchingStrategy:批量更新策略,再看看它的代碼實現(xiàn)
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
可以看到isBatchingUpdates的初始值是false的,在調(diào)用batchedUpdates方法的時候會將isBatchingUpdates變量設置為true。然后根據(jù)設置之前的isBatchingUpdates的值來執(zhí)行不同的流程
還記得上面說的很重要的那段代碼嗎
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
1、首先,點擊事件的處理本身就是在一個大的事務中(這個記著就好),isBatchingUpdates已經(jīng)是true了
2、調(diào)用setState()時,調(diào)用了ReactUpdates.batchedUpdates用事務的方式進行事件的處理
3、在setState執(zhí)行的時候isBatchingUpdates已經(jīng)是true了,setState做的就是將更新都統(tǒng)一push到dirtyComponents數(shù)組中;
4、在事務結束的時候才通過 ReactUpdates.flushBatchedUpdates 方法將所有的臨時 state merge 并計算出最新的 props 及 state,然后將批量執(zhí)行關閉結束事務。
到這里我并沒有順著ReactUpdates.flushBatchedUpdates方法講下去,這部分涉及到渲染和Virtual Dom的內(nèi)容,反正你知道它是拿來執(zhí)行渲染的就行了。
到這里為止,setState的核心概念已經(jīng)比較清楚了,再往下的內(nèi)容,暫時先知道就行了,不然展開來講一環(huán)扣一環(huán)太雜了,我們做事情要把握核心。
到這里不知道有沒有同學想起一個問題 ?
isBatchingUpdates 標志位在 batchedUpdates 發(fā)起的時候被置為 true ,那什么時候被復位為false的呢?
還記得上面的事務的close方法嗎,同一個文件src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();
// 定義復位 wrapper
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
// 定義批更新 wrapper
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});
相信眼尖的同學已經(jīng)看到了,close的時候復位,把isBatchingUpdates設置為false。

Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
var transaction = new ReactDefaultBatchingStrategyTransaction();
通過原型合并,事務的close 方法,將在 enqueueUpdate 執(zhí)行結束后,先把 isBatchingUpdates 復位,再發(fā)起一個 DOM 的批更新
到這里,我們會發(fā)現(xiàn),前面所有的隊列、batchUpdate等等都是為了來到事務的這一步,前面都只是批收集的工作,到這里才真正的完成了批更新的操作。
當然在實際代碼中 React 還做了異常處理等工作,這里不詳細展開。有興趣的同學可以參考源碼中 Transaction 實現(xiàn)。
說了這么多 Transaction,關于上文提到的RESET_BATCHED_UPDATES主要用來管理isBatchingUpdates狀態(tài)這句話是不是;理解更透徹了吶?
上文提到了兩個wrapper:RESET_BATCHED_UPDATES和FLUSH_BATCHED_UPDATES。RESET_BATCHED_UPDATES用來管理isBatchingUpdates狀態(tài),我們前面在分析setState是否立即生效時已經(jīng)講解過了。那FLUSH_BATCHED_UPDATES用來干嘛呢?
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var flushBatchedUpdates = function () {
// 循環(huán)遍歷處理完所有dirtyComponents
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
// close前執(zhí)行完runBatchedUpdates方法,這是關鍵
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
FLUSH_BATCHED_UPDATES會在一個transaction的close階段運行runBatchedUpdates,從而執(zhí)行update。
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
// dirtyComponents中取出一個component
var component = dirtyComponents[i];
// 取出dirtyComponent中的未執(zhí)行的callback,下面就準備執(zhí)行它了
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
if (component._currentElement.props === component._renderedComponent._currentElement) {
namedComponent = component._renderedComponent;
}
}
// 執(zhí)行updateComponent
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
// 執(zhí)行dirtyComponent中之前未執(zhí)行的callback
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
runBatchedUpdates循環(huán)遍歷dirtyComponents數(shù)組,主要干兩件事。首先執(zhí)行performUpdateIfNecessary來刷新組件的view,然后執(zhí)行之前阻塞的callback。下面來看performUpdateIfNecessary。
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// receiveComponent會最終調(diào)用到updateComponent,從而刷新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 執(zhí)行updateComponent,從而刷新View。這個流程在React生命周期中講解過
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
}
最后驚喜的看到了receiveComponent和updateComponent吧。receiveComponent最后會調(diào)用updateComponent,而updateComponent中會執(zhí)行React組件存在期的生命周期方法,如componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 從而完成組件更新的整套流程。
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext,
) {
var inst = this._instance;
invariant(
inst != null,
'Attempted to update component `%s` that has already been unmounted ' +
'(or failed to mount).',
this.getName() || 'ReactCompositeComponent',
);
var willReceive = false;
var nextContext;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;
// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
shouldUpdate = inst.shouldComponentUpdate(
nextProps,
nextState,
nextContext,
);
} else {
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate =
!shallowEqual(prevProps, nextProps) ||
!shallowEqual(inst.state, nextState);
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext,
);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},
_processPendingState: function(props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = Object.assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function'
? partial.call(inst, nextState, props, context)
: partial,
);
}
return nextState;
},
這一部分代碼相對來說不算是很難,replace是存在是由于之前被廢棄的APIthis.replaceState,我們現(xiàn)在不需要關心這一部分,現(xiàn)在我們可以回答剛開始的問題,為什么給setState傳入的參數(shù)是函數(shù)時,就可以解決剛開始的例子。
Object.assign(
nextState,
typeof partial === 'function' ?
partial.call(inst, nextState, props, context) :
partial
);
如果我們傳入的是對象
this.setState({value: this.state.value + 1 });
this.setState({value: this.state.value + 1})
我們現(xiàn)在已經(jīng)知道,調(diào)用setState是批量更新,那么第一次調(diào)用之后,this.state.value的值并沒有改變。兩次更新的value值其實是一樣的,所以達不到我們的目的。但是如果我們傳遞的是回調(diào)函數(shù)的形式,那么情況就不一樣了,partial.call(inst, nextState, props, context)接受的state都是上一輪更新之后的新值,因此可以達到我們預期的目的?!?br>
_processPendingState在計算完新的state之后,會_performComponentUpdate:
function _performComponentUpdate(
nextElement,
nextProps,
nextState,
nextContext,
transaction,
unmaskedContext
) {
var inst = this._instance;
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
var prevProps;
var prevState;
var prevContext;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
prevContext = inst.context;
}
if (inst.componentWillUpdate) {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
this._updateRenderedComponent(transaction, unmaskedContext);
if (hasComponentDidUpdate) {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
inst
);
}
}
我們可以看到,這部分內(nèi)容涉及到了幾方面內(nèi)容,首先在更新前調(diào)用了鉤子函數(shù)componentWillUpdate,然后更新了組件的屬性(props、state、context),執(zhí)行函數(shù)_updateRenderedComponent,最后再次執(zhí)行鉤子函數(shù)componentDidUpdate。
_updateRenderedComponent執(zhí)行組件的render方法。
在文件/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js中,代碼如下:
/**
* Call the component's `render` method and update the DOM accordingly.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
var nextRenderedElement = this._renderValidatedComponent();
var debugID = 0;
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
ReactReconciler.receiveComponent(
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context),
);
} else {
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
ReactReconciler.unmountComponent(prevComponentInstance, false);
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
nextRenderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
var nextMarkup = ReactReconciler.mountComponent(
child,
transaction,
this._hostParent,
this._hostContainerInfo,
this._processChildContext(context),
debugID,
);
this._replaceNodeWithMarkup(
oldHostNode,
nextMarkup,
prevComponentInstance,
);
}
},
/**
* Overridden in shallow rendering.
*
* @protected
*/
_replaceNodeWithMarkup: function(oldHostNode, nextMarkup, prevInstance) {
ReactComponentEnvironment.replaceNodeWithMarkup(
oldHostNode,
nextMarkup,
prevInstance,
);
},
/**
* @protected
*/
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
var renderedElement;
renderedElement = inst.render();
return renderedElement;
},
到目前為止,我們已經(jīng)基本介紹完了setState的更新過程,只剩一個部分沒有介紹,那就是setState執(zhí)行結束之后的回調(diào)函數(shù)。我們知道,setState函數(shù)中如果存在callback,則會有:
if (callback) {
this.updater.enqueueCallback(this, callback);
}
call函數(shù)會被傳遞給this.updater的函數(shù)enqueueCallback,然后非常類似于setState,callback會存儲在組件內(nèi)部實例中的_pendingCallbacks屬性之中。我們知道,回調(diào)函數(shù)必須要setState真正完成之后才會調(diào)用,那么在代碼中是怎么實現(xiàn)的。大家還記得在函數(shù)flushBatchedUpdates中有一個事務ReactUpdatesFlushTransaction:
//代碼有省略
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
if (dirtyComponents.length) {
//從事務pool中獲得事務實例
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
//釋放實例
ReactUpdatesFlushTransaction.release(transaction);
}
//......
}
};
我們現(xiàn)在看看ReactUpdatesFlushTransaction的wrapper是怎么定義的:
var UPDATE_QUEUEING = {
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
},
};
我們看到在事務的close階段定義了this.callbackQueue.notifyAll(),即執(zhí)行了回調(diào)函數(shù),通過這種方法就能保證回調(diào)函數(shù)一定是在setState真正完成之后才執(zhí)行的。到此為止我們基本已經(jīng)解釋了setState大致的流程是怎樣的,但是我們還是沒有回答之前的一個問題,為什么下面的兩種代碼會產(chǎn)生不同的情況:
//未按預期執(zhí)行
_addValue() {
this.setState({
value: this.state.value + 1
})
this.setState({
value: this.state.value + 1
})
}
//按預期執(zhí)行
_addValue() {
setTimeout(()=>{
this.setState({
value: this.state.value + 1
});
this.setState({
value: this.state.value + 1
});
},0)
}
這個問題,其實真的要追本溯源地去講,是比較復雜的,我們簡要介紹一下。在第一種情況下,如果打斷點追蹤你會發(fā)現(xiàn),在第一次執(zhí)行setState前,已經(jīng)觸發(fā)了一個 batchedUpdates,等到執(zhí)行setState時已經(jīng)處于一個較大的事務,因此兩個setState都是會被批量更新的(相當于異步更新的過程,thi.state.value值并沒有立即改變),執(zhí)行setState只不過是將兩者的partialState傳入dirtyComponents,最后再通過事務的close階段的flushBatchedUpdates方法去執(zhí)行重新渲染。但是通過setTimeout函數(shù)的包裝,兩次setState都會在click觸發(fā)的批量更新batchedUpdates結束之后執(zhí)行,這兩次setState會觸發(fā)兩次批量更新batchedUpdates,當然也會執(zhí)行兩個事務以及函數(shù)flushBatchedUpdates,這就相當于一個同步更新的過程,自然可以達到我們的目的,這也就解釋了為什么React文檔中既沒有說setState是同步更新或者是異步更新,只是模糊地說到,setState并不保證同步更新。
舉個??

如下代碼:
class App extends React.Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}
render() {
return <div>{this.state.val}</div>
}
}
// 結果就為 0, 0, 2, 3
- setState 只在合成事件和鉤子函數(shù)中是“異步”的,在原生事件和setTimeout 中都是同步的。
- setState 的“異步”并不是說內(nèi)部由異步代碼實現(xiàn),其實本身執(zhí)行的過程和代碼都是同步的,只是合成事件和- 鉤子函數(shù)的調(diào)用順序在更新之前,導致在合成事件和鉤子函數(shù)中沒法立馬拿到更新后的值,形成了所謂的“異步”,當然可以通過第二個參數(shù) setState(partialState, callback) 中的callback拿到更新后的結果。
- setState的批量更新優(yōu)化也是建立在“異步”(合成事件、鉤子函數(shù))之上的,在原生事件和setTimeout 中不會批量更新,在“異步”中如果對同一個值進行多次setState,setState的批量更新策略會對其進行覆蓋,取最后一次的執(zhí)行,如果是同時setState多個不同的值,在更新時會對其進行合并批量更新。
所以基于上述結論,如果想要實現(xiàn)上述代碼中 4 次 console.log 打印出來的 val 分別是1、2、3、4。可以實現(xiàn)如下:
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 1
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 2
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 3
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 4
}, 0);
或者
this.setState((prevState) => {
return { count: prevState.val + 1 }
})
console.log(this.state.val); // 1
this.setState((prevState) => {
return { count: prevState.val + 1 }
})
console.log(this.state.val); // 2
this.setState((prevState) => {
return { count: prevState.val + 1 }
})
console.log(this.state.val); // 3
this.setState((prevState) => {
return { count: prevState.val + 1 }
})
console.log(this.state.val); // 4
setState 干了什么
1、合成事件中的setState
react為了解決跨平臺,兼容性問題,自己封裝了一套事件機制,代理了原生的事件,像在jsx中常見的onClick、onChange這些都是合成事件。
在react的生命周期和合成事件中,react仍然處于他的更新機制中,這時isBranchUpdate為true。
按照上述過程,這時無論調(diào)用多少次setState,都會不會執(zhí)行更新,而是將要更新的state存入_pendingStateQueue,將要更新的組件存入dirtyComponent。
當上一次更新機制執(zhí)行完畢,以生命周期為例,所有組件,即最頂層組件didmount后會將isBranchUpdate設置為false。這時將執(zhí)行之前累積的setState。
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 輸出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
2、生命周期函數(shù)中的setState
整個生命周期中就是一個事物操作,所以標識位isBatchingUpdates = true,所以流程到了enqueueUpdate()時,實例對象都會加入到dirtyComponents 數(shù)組中
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 輸出的還是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
3、原生事件中的setState
原生事件是指非react合成事件,原生自帶的事件監(jiān)聽 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick 這種綁定事件的形式都屬于原生事件
原生事件綁定不會通過合成事件的方式處理,自然也不會進入更新事務的處理流程。setTimeout也一樣,在setTimeout回調(diào)執(zhí)行時已經(jīng)完成了原更新組件流程,不會放入dirtyComponent進行異步更新,其結果自然是同步的。
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 輸出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
4、setTimeout中的setState
由執(zhí)行機制看,setState本身并不是異步的,而是如果在調(diào)用setState時,如果react正處于更新過程,當前更新會被暫存,等上一次更新執(zhí)行后在執(zhí)行,這個過程給人一種異步的假象。
在生命周期,根據(jù)event loop的模型,會將異步函數(shù)先暫存,等所有同步代碼執(zhí)行完畢后在執(zhí)行,這時上一次更新過程已經(jīng)執(zhí)行完畢,isBranchUpdate被設置為false,根據(jù)上面的流程,這時再調(diào)用setState即可立即執(zhí)行更新,拿到更新結果。
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 輸出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
5、批量更新
在 setState 的時候react內(nèi)部會創(chuàng)建一個 updateQueue ,通過 firstUpdate 、 lastUpdate 、 lastUpdate.next 去維護一個更新的隊列,在最終的 performWork 中,相同的key會被覆蓋,只會對最后一次的 setState 進行更新
分別執(zhí)行以下代碼:
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
componentDidMount() {
this.setState((preState) => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
this.setState(preState => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
}
執(zhí)行結果:
1
1
2
2
說明:
1.直接傳遞對象的setstate會被合并成一次
2.使用函數(shù)傳遞state不會被合并
批量更新中State合并機制
我們看下流程中_processPendingState的代碼,這個函數(shù)是用來合并state暫存隊列的,最后返回一個合并后的state。
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
我們只需要關注下面這段代碼:
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
如果傳入的是對象,很明顯會被合并成一次:
Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)
如果傳入的是函數(shù),函數(shù)的參數(shù)preState是前一次合并后的結果,所以計算結果是準確的。
總結
setState流程還是很復雜的,設計也很精巧,避免了重復無謂的刷新組件。它的主要流程如下:
- enqueueSetState將state放入隊列中,并調(diào)用enqueueUpdate處理要更新的Component;
2.如果組件當前正處于update事務中,則先將Component存入dirtyComponent中。否則調(diào)用batchedUpdates處理。
3.batchedUpdates發(fā)起一次transaction.perform()事務;
4.開始執(zhí)行事務初始化,運行,結束三個階段;
初始化:事務初始化階段沒有注冊方法,故無方法要執(zhí)行;
運行:執(zhí)行setSate時傳入的callback方法,一般不會傳callback參數(shù);
結束:更新isBatchingUpdates為false,并執(zhí)行FLUSH_BATCHED_UPDATES這個wrapper中的close方法。
5.FLUSH_BATCHED_UPDATES在close階段,會循環(huán)遍歷所有的dirtyComponents,調(diào)用updateComponent刷新組件,并執(zhí)行它的pendingCallbacks, 也就是setState中設置的callback。