閱讀源碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪一個版本的時候,我想先接觸到源碼早期的思想可能會更輕鬆一些,最終我選擇閱讀
0.3-stable
。 那麼接下來,我將從幾個方面來解讀這個版本的源碼。javascript
咱們須要直接看到事務的整個概念:html
英文版請看源碼,如下爲 Google 翻譯內容java
Transaction
建立一個黑盒子,它可以包裝任何方法,以便在調用方法以前和以後維護某些不變量(即便在調用包裝方法時拋出異常)。 實例化事務的人能夠在建立時提供不變量的執行者。 Transaction
類自己將爲您提供一個額外的自動不變量 - 任何事務實例在運行時不該該運行的不變量。您一般會建立一個 Transaction
的單個實例,以便屢次重用,這可能用於包裝多個不一樣的方法。包裝器很是簡單 - 它們只須要實現兩種方法。node
/** * <pre> * wrappers (建立時注入) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(任何方法) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->| 任何方法 |---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre> */
複製代碼
獎金:react
用例:算法
componentDidRender
回調。ReactWorker
隊列的特定刷新以保留 scrollTop
(自動滾動感知DOM)。事務性插件 API:數組
initialize
方法的模塊。close
方法。當包裝過程完成或失敗時,調用 close
。// utils/Transaction.js
var Mixin = {
/** * 設置此實例,以便爲收集指標作好準備。 這樣作是爲了使這個設置方法能夠在已經初始化的實例上使用, * 其方式是在重用時不消耗額外的內存。 若是您決定將此 mixin 的子類化爲 "PooledClass" ,那麼這將很是有用。 */
reinitializeTransaction: function() {
// this.getTransactionWrappers() 用於獲取上面概念中的 wrappers
// this.transactionWrappers 就是 wrappers
this.transactionWrappers = this.getTransactionWrappers();
// 初始化用於存儲 initialize 時返回的內容的 this.wrapperInitData
if (!this.wrapperInitData) {
this.wrapperInitData = [];
} else {
this.wrapperInitData.length = 0;
}
// 時間方面,這裏不作解讀
if (!this.timingMetrics) {
this.timingMetrics = {};
}
this.timingMetrics.methodInvocationTime = 0;
if (!this.timingMetrics.wrapperInitTimes) {
this.timingMetrics.wrapperInitTimes = [];
} else {
this.timingMetrics.wrapperInitTimes.length = 0;
}
if (!this.timingMetrics.wrapperCloseTimes) {
this.timingMetrics.wrapperCloseTimes = [];
} else {
this.timingMetrics.wrapperCloseTimes.length = 0;
}
// 初始化事務標記
this._isInTransaction = false;
},
_isInTransaction: false,
getTransactionWrappers: null,
isInTransaction: function() {
return !!this._isInTransaction;
},
// 在安全窗口內執行該功能。 將此用於頂級方法,這些方法會致使須要進行安全檢查的大量計算/突變。
perform: function(method, scope, a, b, c, d, e, f) {
throwIf(this.isInTransaction(), DUAL_TRANSACTION);
var memberStart = Date.now();
// 報錯存儲
var err = null;
// method 返回內容
var ret;
try {
// 開始 initialize
this.initializeAll();
// initialize 完成後調用 method
ret = method.call(scope, a, b, c, d, e, f);
} catch (ie_requires_catch) {
// 抓報錯,這裏報錯會被覆蓋
err = ie_requires_catch;
} finally {
var memberEnd = Date.now();
this.methodInvocationTime += (memberEnd - memberStart);
try {
// 開始 close
this.closeAll();
} catch (closeAllErr) {
// 抓報錯,這裏若前面存在報錯,則取前面的報錯
err = err || closeAllErr;
}
}
// 拋出報錯
if (err) {
throw err;
}
// 返回 method 執行結果
return ret;
},
initializeAll: function() {
// 事務開始
this._isInTransaction = true;
var transactionWrappers = this.transactionWrappers;
var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
var err = null;
// 遍歷 wrappers
for (var i = 0; i < transactionWrappers.length; i++) {
var initStart = Date.now();
var wrapper = transactionWrappers[i];
try {
// 執行 initialize ,並把返回值存入對應的 this.wrapperInitData[i]
this.wrapperInitData[i] =
wrapper.initialize ? wrapper.initialize.call(this) : null;
} catch (initErr) {
err = err || initErr; // Remember the first error.
// 有報錯的話對應位置存入 Transaction.OBSERVED_ERROR
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
} finally {
var curInitTime = wrapperInitTimes[i];
var initEnd = Date.now();
wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
}
}
// 拋出錯誤
if (err) {
throw err;
}
},
/** * 調用 `this.transactionWrappers.close[i]` 函數中的每個,向它們傳遞 * `this.transactionWrappers.init[i]` 的相應返回值(對應於失敗的初始值設定項的 `close`rs 將不被調用)。 */
closeAll: function() {
throwIf(!this.isInTransaction(), MISSING_TRANSACTION);
var transactionWrappers = this.transactionWrappers;
var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
var err = null;
for (var i = 0; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var closeStart = Date.now();
// 獲取 initialize 時對應的返回值
var initData = this.wrapperInitData[i];
try {
// 返回值不等於 Transaction.OBSERVED_ERROR 時才執行 close
if (initData !== Transaction.OBSERVED_ERROR) {
wrapper.close && wrapper.close.call(this, initData);
}
} catch (closeErr) {
err = err || closeErr; // Remember the first error.
} finally {
var closeEnd = Date.now();
var curCloseTime = wrapperCloseTimes[i];
wrapperCloseTimes[i] = (curCloseTime || 0) + (closeEnd - closeStart);
}
}
// 釋放內存
this.wrapperInitData.length = 0;
// 事務關閉
this._isInTransaction = false;
// 拋出錯誤
if (err) {
throw err;
}
}
};
var Transaction = {
Mixin: Mixin,
OBSERVED_ERROR: {}
};
複製代碼
上面這段代碼看完,我相信你對事務的整個概念已經有所瞭解,那麼事務到底在處理什麼的時候用到了呢?接下來就要揭曉本次一樣重點的內容 React 調度事務, ReactReconcileTransaction
:瀏覽器
// core/ReactReconcileTransaction.js
// 這個就是 React 調度事務的 wrappers
var TRANSACTION_WRAPPERS = [
// 確保在可能的狀況下,執行事務不會干擾選擇範圍(當前選定的文本輸入)。
// 通俗點講就是,在 React 調度事務開始之時將選擇的文本信息獲取,再結束時還原選擇信息。
// 關於 Selection 的具體實現這裏不作解讀。
SELECTION_RESTORATION,
// 抑制因爲高級別的DOM操做(如臨時從DOM中刪除文本輸入)而可能無心中調度的事件(模糊/焦點)。
// 通俗點講就是,在 React 調度事務結束以前,抑制事件的傳播。
// 於 core/ReactEventTopLevelCallback.js 中 createTopLevelCallback 函數,關於 _topLevelListenersEnabled 的判斷來抑制。
// 事件控制,稍後作簡單解讀,但內容不會說起事件具體實現相關。
EVENT_SUPPRESSION,
// 提供 `ReactOnDOMReady` 隊列,用於在執行事務期間收集 `onDOMReady` 回調。
ON_DOM_READY_QUEUEING
];
function ReactReconcileTransaction() {
// 初始化事務
this.reinitializeTransaction();
// 這裏涉及到 PooledClass
// 看到 ReactOnDOMReady 時末端也有這樣一句話:
// PooledClass.addPoolingTo(ReactOnDOMReady)
// 調用 getPooled 方法其實就是 new
// 具體在下面解讀 PooledClass
this.reactOnDOMReady = ReactOnDOMReady.getPooled(null);
}
var Mixin = {
// reinitializeTransaction 方法中使用,用於獲取 React 調度事務的 wrappers
getTransactionWrappers: function() {
// 判斷執行環境是否可使用 DOM
if (ExecutionEnvironment.canUseDOM) {
return TRANSACTION_WRAPPERS;
} else {
return [];
}
},
getReactOnDOMReady: function() {
return this.reactOnDOMReady;
},
// 這裏也牽扯到 PooledClass
destructor: function() {
// PooledClass 中 standardReleaser 方法
ReactOnDOMReady.release(this.reactOnDOMReady);
// 釋放內存,一樣的,在 ReactOnDOMReady 中也進行了操做: this._queue = null
this.reactOnDOMReady = null;
}
};
mixInto(ReactReconcileTransaction, Transaction.Mixin);
mixInto(ReactReconcileTransaction, Mixin);
// 這是一個加入 PooledClass 的方法
PooledClass.addPoolingTo(ReactReconcileTransaction);
複製代碼
在看完 ReactReconcileTransaction
其實似懂非懂,不要緊,接下來要依次解讀 PooledClass
, ReactOnDOMReady
。安全
如下關於 PooledClass
僅解讀一個參數的方法,其他多參數狀況都是同樣的。數據結構
// utils/PooledClass.js
var oneArgumentPooler = function(copyFieldsFrom) {
// 至關於執行 CopyConstructor.getPooled()
// CopyConstructor 就是 addPoolingTo 傳入的第一參數
var Klass = this;
// 一但執行過 release , CopyConstructor.instancePool.length 纔會大於 0
if (Klass.instancePool.length) {
// 這個 instance 是作什麼用的,相信看到 call ,你變會理解。
// 用來綁定執行上下文。
var instance = Klass.instancePool.pop();
// 這裏再也不進行實例化,而是以 instance 爲執行上下文直接調用,並將其返回。
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
// 至關於直接 new CopyConstructor(copyFieldsFrom)
return new Klass(copyFieldsFrom);
}
};
var standardReleaser = function(instance) {
var Klass = this;
// 在 CopyConstructor.getPooled() 後,保存返回結果
// 在 release 時做爲參數傳入,若存在 destructor 則執行。
if (instance.destructor) {
instance.destructor();
}
// 長度未超過默認大小,則將 instance 推入數組
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
};
// 默認的大小
var DEFAULT_POOL_SIZE = 10;
// 默認的 getPooled 方法
var DEFAULT_POOLER = oneArgumentPooler;
// 直接在 CopyConstructor 上添加屬性的方法,並返回 CopyConstructor
var addPoolingTo = function(CopyConstructor, pooler) {
var NewKlass = CopyConstructor;
NewKlass.instancePool = [];
NewKlass.getPooled = pooler || DEFAULT_POOLER;
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
var PooledClass = {
addPoolingTo: addPoolingTo,
oneArgumentPooler: oneArgumentPooler,
twoArgumentPooler: twoArgumentPooler,
fiveArgumentPooler: fiveArgumentPooler
};
複製代碼
要說 PooledClass
是作什麼用的,具體我也仍是沒有 get 到,可是通過個人實踐,在調用 var c = CopyConstructor.getPooled()
後,若在 c
上添加屬性,如: c.v = '0.3'
。而且在調用 CopyConstructor.release(c)
這樣的狀況,在從新進行 CopyConstructor.getPooled()
時,這個 v
屬性及值任然存在,固然前提是,你本身定義的 destructor
方法裏不會銷燬 v
的值。
那麼如今咱們來看下 ReactOnDOMReady
:
// core/ReactOnDOMReady.js
// 初始化隊列
function ReactOnDOMReady(initialCollection) {
this._queue = initialCollection || null;
}
mixInto(ReactOnDOMReady, {
// 入隊
enqueue: function(component, callback) {
this._queue = this._queue || [];
// 組件及回調
this._queue.push({component: component, callback: callback});
},
// 執行隊列
notifyAll: function() {
var queue = this._queue;
if (queue) {
// 清空隊列
this._queue = null;
for (var i = 0, l = queue.length; i < l; i++) {
var component = queue[i].component;
var callback = queue[i].callback;
// 回調傳入組件,上下文綁定 component
// 關於 getDOMNode 方法稍後解讀(得到當前 component 的 node)
callback.call(component, component.getDOMNode());
}
queue.length = 0;
}
},
// 清空隊列
reset: function() {
this._queue = null;
},
// PooledClass.release 時候使用
destructor: function() {
this.reset();
}
});
// 添加 PooledClass 方法
PooledClass.addPoolingTo(ReactOnDOMReady);
複製代碼
這段代碼很簡單了, ReactOnDOMReady
就是一個執行隊列。回顧到上面代碼發現, React 調度事務在被初始化的時候,一樣 ReactOnDOMReady
也被初始化,在調度事務執行 wrappers 的過程時, ReactOnDOMReady
被相繼執行。這個具體的過程,你看到 ON_DOM_READY_QUEUEING
變會明白。
// core/ReactReconcileTransaction.js
var ON_DOM_READY_QUEUEING = {
// 在 React 調度事務 initialize 時, ReactOnDOMReady 隊列被重置。
initialize: function() {
this.reactOnDOMReady.reset();
},
// 在 React 調度事務 close 時, ReactOnDOMReady 隊列被依次執行。
close: function() {
this.reactOnDOMReady.notifyAll();
}
};
複製代碼
一樣的,在 React 調度事務執行 release
時, ReactOnDOMReady
也會執行 release
。
差點忘了提下, getDOMNode
方法是作什麼用的,請直接看到源碼:
// core/ReactComponent.js
var ReactComponent = {
Mixin: {
getDOMNode: function() {
// 嘗試得到 _rootNode
var rootNode = this._rootNode;
if (!rootNode) {
// 嘗試得到 _rootNodeID
rootNode = document.getElementById(this._rootNodeID);
if (!rootNode) {
// TODO: Log the frequency that we reach this path.
// 這裏代碼就不作詳細解讀了,反正就是爲了得到對應的 Node ,一級級往下查找。
rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
}
// 對其進行賦值,用於下次查詢。
this._rootNode = rootNode;
}
return rootNode;
},
}
};
複製代碼
那麼如今,咱們來看到 SELECTION_RESTORATION
:
// core/ReactReconcileTransaction.js
var EVENT_SUPPRESSION = {
initialize: function() {
// 獲取 ReactEvent.isEnabled() 用於 close 時接收。
// 其實就是 true
var currentlyEnabled = ReactEvent.isEnabled();
// 設置爲 false
ReactEvent.setEnabled(false);
return currentlyEnabled;
},
// close 時設置爲 true
close: function(previouslyEnabled) {
ReactEvent.setEnabled(previouslyEnabled);
}
};
複製代碼
那麼在這個真個 wrappers 執行過程,這個有什麼用呢? 這須要看到 ReactEventTopLevelCallback
的一段分支邏輯:
// core/ReactEventTopLevelCallback.js
var ReactEventTopLevelCallback = {
createTopLevelCallback: function(topLevelType) {
return function(fixedNativeEvent) {
// setEnabled 方法修改的就是 _topLevelListenersEnabled 的值。
if (!_topLevelListenersEnabled) {
return;
}
// 直接掉事後續邏輯
};
}
};
複製代碼
那麼到此,咱們來回顧一下,以前說到的事務機制的運用是如何進行的,從新看到這段代碼是否是會清晰了不少:
// core/ReactComponent.js
var ReactComponent = {
Mixin: {
mountComponentIntoNode: function(rootID, container) {
// 初始化 React 調度事務
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
// 進入 React 調度事務 wrappers 環節
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction
);
// 銷燬這個 React 調度事務
ReactComponent.ReactReconcileTransaction.release(transaction);
// 整個掛載方法結束
}
}
}
複製代碼
這裏附一張此方法的圖:
/** * <pre> * TRANSACTION_WRAPPERS (ExecutionEnvironment.canUseDOM 爲 true 的狀況) * + + + * | | | * +-----------------|----------------|-----|-------------------+ * | v | | | * | +-----------------------+ | | | * | +--| SELECTION_RESTORATION |---|-----|---+ | * | | +-----------------------+ v | | | * | | +-------------------+ | | | * | | +-------| EVENT_SUPPRESSION |--|---|-----+ | * | | | +-------------------+ v | | | * | | | +-----------------------+ | | | * | | | +--| ON_DOM_READY_QUEUEING |-|-----|-----+ | * | | | | +-----------------------+ | | | | * | | | | | | | | * perform(_mount | v v v v v v | wrapper * Component | +---+ +---+ +---+ +----------------+ +---+ +---+ +---+ | invariants * IntoNode) | | | | | | | | | | | | | | | | maintained * +------------------>|-|---|-|---|-|---|-->| _mount |---|---|-|---|-|---|-|--------> * | | | | | | | | Component | | | | | | | | * | | | | | | | | IntoNode | | | | | | | | * | | | | | | | | | | | | | | | | * | +---+ +---+ +---+ +----------------+ +---+ +---+ +---+ | * | initialize close | * +------------------------------------------------------------+ * </pre> */
複製代碼
那麼至此,實現事務機制及 React 調度事務。
咱們知道瀏覽器渲染引擎是單線程的,在 React 15.x 版本及以前版本,計算組件樹變動時將會阻塞整個線程,整個渲染過程是連續不中斷完成的,而這時的其餘任務都會被阻塞,如動畫等,這可能會使用戶感受到明顯卡頓,好比當你在訪問某一網站時,輸入某個搜索關鍵字,更優先的應該是交互反饋或動畫效果,若是交互反饋延遲 200ms ,用戶則會感受較明顯的卡頓,而數據響應晚200毫秒並沒太大問題。這個版本的調和器能夠稱爲棧調和器( Stack Reconciler ),其調和算法大體過程見 React Diff 算法 和 React Stack Reconciler 實現。
Stack Reconcilier 的主要缺陷就是不能暫停渲染任務,也不能切分任務,沒法有效平衡組件更新渲染與動畫相關任務間的執行順序,即不能劃分任務優先級,有可能致使重要任務卡頓,動畫掉幀等問題。
React 16 版本提出了一個更先進的調和器,它容許渲染進程分段完成,而沒必要須一次性完成,中間能夠返回至主進程控制執行其餘任務。而這是經過計算部分組件樹的變動,並暫停渲染更新,詢問主進程是否有更高需求的繪製或者更新任務須要執行,這些高需求的任務完成後纔開始渲染。這一切的實現是在代碼層引入了一個新的數據結構 - Fiber 對象,每個組件實例對應有一個 fiber 實例,此 fiber 實例負責管理組件實例的更新,渲染任務及與其餘 fiber 實例的聯繫。
這個新推出的調和器就叫作纖維調和器( Fiber Reconciler ),它提供的新功能主要有: