上一篇文章講到了React 調用ReactDOM.render
首次渲染組件的前幾個過程的源碼, 包括建立元素、根據元素實例化對應組件, 利用事務來進行批量更新. 咱們還穿插介紹了React 事務的實現以及如何利用事務進行批量更新的實現. 這篇文章咱們接着分析後面的過程, 包括調用了哪些事務, 組件插入的過程, 組件生命週期方法何時被調用等.javascript
在React 源碼中, 首次渲染組件有一個重要的過程, mount
, 插入, 即插入到DOM中, 發生在實例化組件以後. React使用批量策略來管理組件插入到DOM的過程. 這個「批量」不是指像遍歷數組那樣同批次插入, 而是一個不斷生成不斷插入、相似遞歸的過程. 讓咱們一步一步來分析.java
如何管理呢? 即在插入以前就開始一次batch, 而後插入過程當中任何更新都會被enqueue, 在batchingStrategy事務的close階段批量更新.web
咱們來看首先在插入以前的準備, ReactMount.js中, batchedMountComponentIntoNode
被放到了批量策略batchedUpdates
中執行 :數組
// 放在批量策略batchedUpdates中執行插入
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
...
);
複製代碼
從上篇文章展現的源碼中看到, 這個batchingStrategy
就是ReactDefaultBatchingStrategy
, 所以調用了ReactDefaultBatchingStrategy
的batchedUpdates
, 並將batchedMountComponentIntoNode
看成callback.app
繼續看ReactDefaultBatchingStrategy
的batchedUpdates
, 在ReactDefaultBatchingStrategy.js :函數
// 批處理策略
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false, // 是否處在一次BatchingUpdates標誌位
// 批量更新策略調用的就是這個方法
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 一旦調用批處理, 重置isBatchingUpdates標誌位, 表示正處在一次BatchingUpdates中
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 首次插入時, 因爲是第一次啓動批量策略, 所以alreadyBatchingUpdates爲false, 執行事務
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e); // 將callback放進事務裏執行
}
},
};
複製代碼
咱們在componentWillMount
裏setState, 看看React會怎麼作:性能
// ReactBaseClasses.js :
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
//ReactUpdateQueue.js:
enqueueSetState: function(publicInstance, partialState) {
// enqueueUpdate
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
//ReactUpdate.js:
function enqueueUpdate(component) {
ensureInjected(); // 注入默認策略
// 若是不是在一次batch就開啓一次batch
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 若是是就存儲更新
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
複製代碼
在ReactUpdates.js中優化
var flushBatchedUpdates = function () {
// 批量處理dirtyComponents
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
// 批量處理callback
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
複製代碼
batchedUpdates
啓動一個策略事務去執行batchedMountComponentIntoNode
, 以便利用策略控制更新, 而在這個函數中又啓動了一個調和(Reconcile)事務, 以便管理插入.this
// ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();
...
var ReactDefaultBatchingStrategy = {
...
batchedUpdates: function(callback, a, b, c, d, e) {
...
// 啓動ReactDefaultBatchingStrategy事務
return transaction.perform(callback, null, a, b, c, d, e);
},
};
// ReactMount.js
function batchedMountComponentIntoNode( ... ) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,
);
// 啓動Reconcile事務
transaction.perform(
mountComponentIntoNode,
...
);
...
}
複製代碼
在ReactMount.js :google
function batchedMountComponentIntoNode( componentInstance, container, shouldReuseMarkup, context, ) {
// 從對象池中拿到ReactReconcileTransaction事務
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement,
);
// 啓動事務執行mountComponentIntoNode
transaction.perform(
mountComponentIntoNode,
null,
componentInstance,
container,
transaction,
shouldReuseMarkup,
context,
);
// 釋放事務
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
複製代碼
React 在啓動另外一個事務以前拿到了這個事務, 從哪裏拿到的呢? 這裏就涉及到了React 優化策略之一——對象池
首先你用JavaScript聲明的變量再也不使用時, js引擎會在某些時間回收它們, 這個回收時間是耗時的. 資料顯示:
Marking latency depends on the number of live objects that have to be marked, with marking of the whole heap potentially taking more than 100 ms for large webpages.
整個堆的標記對於大型網頁極可能須要超過100毫秒
儘管V8引擎對垃圾回收有優化, 但爲了不重複建立臨時對象形成GC不斷啓動以及複用對象, React使用了對象池來複用對象, 對GC代表, 我一直在使用它們, 請不要啓動回收.
React 實現的對象池其實就是對類進行了包裝, 給類添加一個實例隊列, 用時取, 不用時再放回, 防止重複實例化:
PooledClass.js :
// 添加對象池, 實質就是對類包裝
var addPoolingTo = function (CopyConstructor, pooler) {
// 拿到類
var NewKlass = CopyConstructor;
// 添加實例隊列屬性
NewKlass.instancePool = [];
// 添加拿到實例方法
NewKlass.getPooled = pooler || DEFAULT_POOLER;
// 實例隊列默認爲10個
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
// 將實例放回隊列
NewKlass.release = standardReleaser;
return NewKlass;
};
// 從對象池申請一個實例.對於不一樣參數數量的類,React分別處理, 這裏是一個參數的類的申請實例的方法, 其餘同樣
var oneArgumentPooler = function(copyFieldsFrom) {
// this 指的就是傳進來的類
var Klass = this;
// 若是類的實例隊列有實例, 則拿出來一個
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else { // 不然說明是第一次實例化, new 一個
return new Klass(copyFieldsFrom);
}
};
// 釋放實例到類的隊列中
var standardReleaser = function(instance) {
var Klass = this;
...
// 調用類的解構函數
instance.destructor();
// 放到隊列
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
};
// 使用時將類傳進去便可
PooledClass.addPoolingTo(ReactReconcileTransaction);
複製代碼
能夠看到, React對象池就是給類維護一個實例隊列, 用到就pop一個, 不用就push回去. 在React源碼中, 用完實例後要當即釋放, 也就是申請和釋放成對出現, 達到優化性能的目的.
在ReactMount.js中, mountComponentIntoNode
函數執行了組件實例的mountComponent
, 不一樣的組件實例有本身的mountComponent方法, 作的也是不一樣的事情. (源碼我就不上了, 太TM…)
ReactCompositeComponent類型的mountComponent方法:
ReactDOMComponent類型:
ReactDOMTextComponent類型:
整個mount過程是遞歸渲染的(矢量圖):
剛開始, React給要渲染的組件從最頂層加了一個ReactCompositeComponent類型的 topLevelWrapper來方便的存儲全部更新, 所以初次遞歸是從 ReactCompositeComponent 的mountComponent
開始的, 這個過程會調用組件的render函數(若是有的話), 根據render出來的elements再調用instantiateReactComponent
實例化不一樣類型的組件, 再調用組件的 mountComponent
, 所以這是一個不斷渲染不斷插入、遞歸的過程.
React 初始渲染主要分爲如下幾個步驟:
createElements
建立這個組件elements tree. 在這個subtree中, 裏層建立出來的元素做爲包裹層的props.children;mountComponent
.
mountComponent
過程當中會先調用render(Composite類型 )生成組件的elements tree, 而後順着props.children, 不斷實例化, 不斷調用各自組件的mountComponent 造成循環