閱讀原始碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪個版本的時候,我想先接觸到原始碼早期的思想可能會更輕鬆一些,最終我選擇閱讀
0.3-stable
。 那麼接下來,我將從幾個方面來解讀這個版本的原始碼。
- React 原始碼學習(一):HTML 元素渲染
- React 原始碼學習(二):HTML 子元素渲染
- React 原始碼學習(三):CSS 樣式及 DOM 屬性
- React 原始碼學習(四):事務機制
- React 原始碼學習(五):事件機制
- React 原始碼學習(六):元件渲染
- React 原始碼學習(七):生命週期
- React 原始碼學習(八):元件更新
我們先來看到整體邏輯圖:
React 採用將事件掛載至 document
或者 window
上來實現頂級事件。接下來我們會一一來介紹事件的實現過程。
我們來回憶下, ReactNativeComponent.js
中 _createOpenTagMarkup
方法中的 putListener
函式和 ReactMount.js
中 renderComponent
方法中的 prepareTopLevelEvents
函式都是事件相關的方法,前者是生成 HTML 標記時,對符合要求的“鍵”進行註冊事件,後者則是在執行 React.renderComponent
方法時進行準備頂級事件的過程。當然之前有一個內容沒有提到,就是在 React 注入時,預設注入了事件外掛排序順序、例項操作函式、事件外掛。那讓我們直接先看到程式碼:
// core/ReactDefaultInjection.js
function inject() {
// 注入模組,用於解析DOM層次結構和外掛排序。
// 這裡是注入事件外掛排序順序
EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
// 注入例項操作函式,用於事件傳播
EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
// 預設包含兩個重要事件外掛(而不必要求他們)。
// 注入事件外掛
EventPluginHub.injection.injectEventPluginsByName({
'SimpleEventPlugin': SimpleEventPlugin,
'EnterLeaveEventPlugin': EnterLeaveEventPlugin
});
// 用於相容 IE8 建立的複合元件,後續講到元件的時候再做解讀,但不對 `ReactDOMForm.js` 進行解讀
ReactDOM.injection.injectComponentClasses({
form: ReactDOMForm
});
}
複製程式碼
當然你需要知道,這個 inject
函式時需要在 React.js
中引入並執行的。
我們一步步往下看。先來看看 DefaultEventPluginOrder.js
:
// eventPlugins/DefaultEventPluginOrder.js
var DefaultEventPluginOrder = [
keyOf({ResponderEventPlugin: null}),
keyOf({SimpleEventPlugin: null}),
keyOf({TapEventPlugin: null}),
keyOf({EnterLeaveEventPlugin: null}),
keyOf({AnalyticsEventPlugin: null})
];
複製程式碼
就是一個事件外掛的預設順序列表,使用 EventPluginHub.injection.injectEventPluginOrder
方法進行注入。
// event/EventPluginHub.js
var injection = {
EventPluginOrder: null,
injectEventPluginOrder: function(InjectedEventPluginOrder) {
injection.EventPluginOrder = InjectedEventPluginOrder;
// 這個方法稍後再做解讀,因為 injectEventPluginsByName 方法也使用到了他
injection._recomputePluginsList();
}
};
複製程式碼
EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
也是差不多的操作,他操作的是 EventPropagators.js
下的 InstanceHandle
。 ReactInstanceHandles
在後續事件相關中都會使用到。我們先來看看 injectEventPluginsByName
方法:
// event/EventPluginHub.js
var injection = {
plugins: [],
injectEventPluginsByName: function(pluginsByName) {
// 這裡講傳入外掛與已存外掛進行合併
injection.pluginsByName = merge(injection.pluginsByName, pluginsByName);
injection._recomputePluginsList();
},
pluginsByName: {},
// 重新計算事件外掛順序列表
_recomputePluginsList: function() {
var injectPluginByName = function(name, PluginModule) {
// 查詢外掛順序列表,不存在則丟擲錯誤
var pluginIndex = injection.EventPluginOrder.indexOf(name);
throwIf(pluginIndex === -1, ERRORS.DEPENDENCY_ERROR + name);
// 對應的外掛列表不存在的情況,則在對應索引賦值其對應的外掛模組
if (!injection.plugins[pluginIndex]) {
injection.plugins[pluginIndex] = PluginModule;
// 遍歷其外掛模組對應的抽象事件型別
for (var eventName in PluginModule.abstractEventTypes) {
var eventType = PluginModule.abstractEventTypes[eventName];
// 記錄所有的註冊名稱
recordAllRegistrationNames(eventType, PluginModule);
}
}
};
// 事件外掛的預設順序列表存在情況
if (injection.EventPluginOrder) { // Else, do when plugin order injected
var injectedPluginsByName = injection.pluginsByName;
// 遍歷注入的外掛
for (var name in injectedPluginsByName) {
injectPluginByName(name, injectedPluginsByName[name]);
}
}
}
};
var registrationNames = {};
var registrationNamesArr = [];
function recordAllRegistrationNames(eventType, PluginModule) {
var phaseName;
// 從 eventType 獲得 phasedRegistrationNames
var phasedRegistrationNames = eventType.phasedRegistrationNames;
// 檢視相關原始碼可以發現,這裡適用於 `SimpleEventPlugin`
if (phasedRegistrationNames) {
// 遍歷 phasedRegistrationNames
for (phaseName in phasedRegistrationNames) {
if (!phasedRegistrationNames.hasOwnProperty(phaseName)) {
continue;
}
// 對應位置進行新增
registrationNames[phasedRegistrationNames[phaseName]] = PluginModule;
registrationNamesArr.push(phasedRegistrationNames[phaseName]);
}
} else if (eventType.registrationName) {
// 若註冊名稱在的情況,檢視相關原始碼可以發現,這裡適用於 `EnterLeaveEventPlugin`
registrationNames[eventType.registrationName] = PluginModule;
registrationNamesArr.push(eventType.registrationName);
}
// 看到原始碼專案結構或者事件外掛的預設順序列表你都會發現,有著 `ResponderEventPlugin`
// `TapEventPlugin`, `AnalyticsEventPlugin` 的存在。
}
複製程式碼
比較抽象對吧,因為這裡操作的都是事件外掛的預設順序列表中的物件,我們接下來看到 SimpleEventPlugin
和 EnterLeaveEventPlugin
來做相應的解釋:
// eventPlugins/SimpleEventPlugin.js
var SimpleEventPlugin = {
abstractEventTypes: {
// Note: We do not allow listening to mouseOver events. Instead, use the
// onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
// 這裡提到 onMouseEnter/onMouseLeave 請檢視到 `EnterLeaveEventPlugin.js`
mouseDown: {
// 這是上面註冊所有事件名稱使用到的位置
phasedRegistrationNames: {
bubbled: keyOf({onMouseDown: true}),
captured: keyOf({onMouseDownCapture: true})
}
},
// ...
}
};
複製程式碼
// eventPlugins/EnterLeaveEventPlugin.js
var abstractEventTypes = {
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
};
複製程式碼
那麼從中獲取相應的物件如 phasedRegistrationNames
或是對應的標識 registrationName
。
我們現在來看到 ReactMount.js
中 renderComponent
方法中的 prepareTopLevelEvents
函式:
// core/ReactMount.js
var ReactMount = {
useTouchEvents: false,
prepareTopLevelEvents: function(TopLevelCallbackCreator) {
ReactEvent.ensureListening(
// 用於控制是否使用 TouchEvents , 預設為 false
ReactMount.useTouchEvents,
TopLevelCallbackCreator
);
}
};
複製程式碼
但是你可以呼叫 React.initializeTouchEvents(true);
來提前開啟他,當然這是你需要使用到他的時候。
// core/ReactEvent.js
var _isListening = false;
function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
if (!_isListening) {
// 賦值頂級回撥建立者
ReactEvent.TopLevelCallbackCreator = TopLevelCallbackCreator;
listenAtTopLevel(touchNotMouse);
// 一個私有變數,用於控制 ensureListening 方法只執行一次
_isListening = true;
}
}
function listenAtTopLevel(touchNotMouse) {
var mountAt = document;
registerDocumentScrollListener();
registerDocumentResizeListener();
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
// ...
// TouchEvents 相關
if (touchNotMouse) {
trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
// ...
}
// ... 以及一些相容相關,涉及使用到 `isEventSupported` 方法
}
複製程式碼
// event/EventConstants.js
var topLevelTypes = keyMirror({
topBlur: null,
// ...
});
複製程式碼
上面程式碼直接完成頂級事件的監聽,我們順勢來看看 trapBubbledEvent
,與之相關的還有 trapCapturedEvent
,分別是冒泡和捕獲階段:
// core/ReactEvent.js
function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
listen(
onWhat,
handlerBaseName,
ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
);
}
function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
capture(
onWhat,
handlerBaseName,
ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
);
}
複製程式碼
其實從這裡你就可以發現,不管是冒泡或者是捕獲,我在 document
或者 window
這樣的頂級物件上,都會先被捕獲到,至於最後想到哪個方法或者函式,那麼是由事件傳播器控制,查詢對應的回撥登錄檔來完成功能。
// event/NormalizedEventListener.js
function createNormalizedCallback(cb) {
return function(unfixedNativeEvent) {
cb(normalizeEvent(unfixedNativeEvent));
};
}
var NormalizedEventListener = {
listen: function(el, handlerBaseName, cb) {
EventListener.listen(el, handlerBaseName, createNormalizedCallback(cb));
},
capture: function(el, handlerBaseName, cb) {
EventListener.capture(el, handlerBaseName, createNormalizedCallback(cb));
}
};
複製程式碼
normalizeEvent
函式是一個補丁,這裡不做解讀。你可以直接忽略理解為 cb(unfixedNativeEvent);
。這裡呼叫的又是另一個模組 EventListener
, EventListener
則的對原生瀏覽器 API 進行了封裝。
// vendor/stubs/EventListener.js
var EventListener = {
listen: function(el, handlerBaseName, cb) {
if (el.addEventListener) {
el.addEventListener(handlerBaseName, cb, false);
} else if (el.attachEvent) {
el.attachEvent('on' + handlerBaseName, cb);
}
},
capture: function(el, handlerBaseName, cb) {
if (!el.addEventListener) {
// ...
return;
} else {
el.addEventListener(handlerBaseName, cb, true);
}
}
};
複製程式碼
那麼由此可以看出, trapBubbledEvent
在操作的其實就是 addEventListener
API,唯獨還沒有解讀的是 ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
,其實你會發現,從剛剛開始我都沒有解讀 TopLevelCallbackCreator
,那麼我們現在就來聊聊:
// core/ReactEventTopLevelCallback.js
var ReactEventTopLevelCallback = {
// 這裡的 `topLevelType` 的值已經在 `listenAtTopLevel` 函式執行階段就完成了賦值
// 當 `addEventListener` 執行後,返回的是 `unfixedNativeEvent` 引數
// 經過由 `normalizeEvent` 補丁處理後,返回 `fixedNativeEvent`
createTopLevelCallback: function(topLevelType) {
return function(fixedNativeEvent) {
// 這個在之前也有提到過,當執行 React 排程事務時,將控制事件
if (!_topLevelListenersEnabled) {
return;
}
// 獲得第一個 React DOM
var renderedTarget = ReactInstanceHandles.getFirstReactDOM(
fixedNativeEvent.target
) || ExecutionEnvironment.global;
// 獲得對應的 ID
var renderedTargetID = getDOMNodeID(renderedTarget);
var event = fixedNativeEvent;
var target = renderedTarget;
// 例如: ReactEvent.handleTopLevel('topClick', event, '.reactRoot[0]', target)
ReactEvent.handleTopLevel(topLevelType, event, renderedTargetID, target);
};
}
};
複製程式碼
// core/ReactEvent.js
function handleTopLevel(
topLevelType,
nativeEvent,
renderedTargetID,
renderedTarget) {
// 提取抽象事件,返回 AbstractEvents 例項
// 這個抽象事件可能是 AbstractEvents 或者可能是 AbstractEvents[]
var abstractEvents = EventPluginHub.extractAbstractEvents(
topLevelType,
nativeEvent,
renderedTargetID,
renderedTarget
);
// The event queue being processed in the same cycle allows preventDefault.
// 加入抽象事件佇列
EventPluginHub.enqueueAbstractEvents(abstractEvents);
EventPluginHub.processAbstractEventQueue();
}
複製程式碼
// event/EventPluginHub.js
var extractAbstractEvents = function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
var abstractEvents;
// 重新計算的 plugins 列表,就是執行 _recomputePluginsList 函式後的結果
var plugins = injection.plugins;
var len = plugins.length;
// 遍歷外掛列表
for (var i = 0; i < len; i++) {
// Not every plugin in the ordering may be loaded at runtime.
var possiblePlugin = plugins[i];
// possiblePlugin 存在的情況下執行
// 這裡的 extractAbstractEvents 方法是 possiblePlugin 的
// 比如 SimpleEventPlugin.extractAbstractEvents 方法
// 對 SimpleEventPlugin.extractAbstractEvents 或者是 EnterLeaveEventPlugin.extractAbstractEvents
// 這裡不做具體解讀,但是需要提到的是,進過函式返回的內容是 AbstractEvent 的例項,他使用 PooledClass 注入過
// 可以直接呼叫 getPooled 及 release 方法
var extractedAbstractEvents =
possiblePlugin &&
possiblePlugin.extractAbstractEvents(
topLevelType,
nativeEvent,
renderedTargetID,
renderedTarget
);
// 若最終這個返回值存在
if (extractedAbstractEvents) {
// 合併 abstractEvents 和 extractedAbstractEvents
abstractEvents = accumulate(abstractEvents, extractedAbstractEvents);
}
}
return abstractEvents;
};
var abstractEventQueue = [];
var enqueueAbstractEvents = function(abstractEvents) {
if (abstractEvents) {
// 對這個抽象事件佇列進行合併
abstractEventQueue = accumulate(abstractEventQueue, abstractEvents);
}
};
var processAbstractEventQueue = function() {
var processingAbstractEventQueue = abstractEventQueue;
// 釋放記憶體
abstractEventQueue = null;
// 若 processingAbstractEventQueue 是陣列則遍歷執行 executeDispatchesAndRelease 函式
// 反之 executeDispatchesAndRelease(processingAbstractEventQueue)
forEachAccumulated(processingAbstractEventQueue, executeDispatchesAndRelease);
};
var executeDispatchesAndRelease = function(abstractEvent) {
if (abstractEvent) {
// 獲得對應的註冊事件名稱
var PluginModule = getPluginModuleForAbstractEvent(abstractEvent);
// 比如獲得了 SimpleEventPlugin.executeDispatch ,用於實現與預設實現相同,但在返回值為 false 時取消事件除外的功能
var pluginExecuteDispatch = PluginModule && PluginModule.executeDispatch;
EventPluginUtils.executeDispatchesInOrder(
abstractEvent,
pluginExecuteDispatch || EventPluginUtils.executeDispatch
);
// 銷燬抽象事件
AbstractEvent.release(abstractEvent);
}
};
function getPluginModuleForAbstractEvent(abstractEvent) {
if (abstractEvent.type.registrationName) {
return registrationNames[abstractEvent.type.registrationName];
} else {
for (var phase in abstractEvent.type.phasedRegistrationNames) {
if (!abstractEvent.type.phasedRegistrationNames.hasOwnProperty(phase)) {
continue;
}
var PluginModule = registrationNames[
abstractEvent.type.phasedRegistrationNames[phase]
];
if (PluginModule) {
return PluginModule;
}
}
}
return null;
}
複製程式碼
這裡將對 SimpleEventPlugin.extractAbstractEvents
或者是 EnterLeaveEventPlugin.extractAbstractEvents
進行解讀。
// eventPlugins/SimpleEventPlugin.js
var SimpleEventPlugin = {
// 'topClick', event, '.reactRoot[0]', target
extractAbstractEvents: function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
var data;
// SimpleEventPlugin.abstractEventTypes 的對映
var abstractEventType =
SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
if (!abstractEventType) {
return null;
}
switch(topLevelType) {
// ...
}
// 例項化 AbstractEvent
var abstractEvent = AbstractEvent.getPooled(
abstractEventType,
renderedTargetID,
topLevelType,
nativeEvent,
data
);
// 這裡是個關鍵
// 對捕獲和冒泡分別進行派發這個 AbstractEvent 例項
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
}
};
SimpleEventPlugin.topLevelTypesToAbstract = {
topMouseDown: SimpleEventPlugin.abstractEventTypes.mouseDown,
// ...
};
複製程式碼
在 EnterLeaveEventPlugin.js
中, extractAbstractEvents
方法返回了 [leave, enter]
的 AbstractEvent 例項化,並且呼叫 EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
進行派發。
EventPropagators.js
相關的方法稍後做解讀。
那麼在之前呼叫 executeDispatchesAndRelease
函式時,執行相應函式涉及到的函式如下:
// event/EventPluginUtils.js
function executeDispatch(abstractEvent, listener, domID) {
// forEachEventDispatch 函式中對應的 cb(abstractEvent, dispatchListeners, dispatchIDs)
listener(abstractEvent, domID);
}
function executeDispatchesInOrder(abstractEvent, executeDispatch) {
forEachEventDispatch(abstractEvent, executeDispatch);
abstractEvent._dispatchListeners = null;
abstractEvent._dispatchIDs = null;
}
function forEachEventDispatch(abstractEvent, cb) {
// 在初始化的時候,這兩個內容都是被置為 null 的
// 他們都在 EventPropagators 被賦值
// 若存在的情況下,這就是你在元素上定義的事件回撥及對應的 ID
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
if (Array.isArray(dispatchListeners)) {
var i;
for (
i = 0;
i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
i++) {
// Listeners and IDs are two parallel arrays that are always in sync.
cb(abstractEvent, dispatchListeners[i], dispatchIDs[i]);
}
} else if (dispatchListeners) {
cb(abstractEvent, dispatchListeners, dispatchIDs);
}
}
複製程式碼
大家對 AbstractEvent
的屬性有些疑問,因為剛剛在提到的時候並沒有提到 AbstractEvent
的實現。那麼接下來解釋下,這樣能把全部都串聯起來:
// event/AbstractEvent.js
var MAX_POOL_SIZE = 20;
function AbstractEvent(
abstractEventType,
abstractTargetID, // Allows the abstract target to differ from native.
originatingTopLevelEventType,
nativeEvent,
data) {
// SimpleEventPlugin.abstractEventTypes.click, .reactRoot[0]', 'topClick', event, data
this.type = abstractEventType;
this.abstractTargetID = abstractTargetID || '';
this.originatingTopLevelEventType = originatingTopLevelEventType;
this.nativeEvent = nativeEvent;
this.data = data;
this.target = nativeEvent && nativeEvent.target;
this._dispatchListeners = null;
this._dispatchIDs = null;
this.isPropagationStopped = false;
}
AbstractEvent.poolSize = MAX_POOL_SIZE;
AbstractEvent.prototype.destructor = function() {
this.target = null;
this._dispatchListeners = null;
this._dispatchIDs = null;
};
PooledClass.addPoolingTo(AbstractEvent, PooledClass.fiveArgumentPooler);
複製程式碼
現在我們來看到剛剛忽略掉的 EventPropagators.accumulateTwoPhaseDispatches
和 EventPropagators.accumulateEnterLeaveDispatches
方法:
// event/EventPropagators.js
function accumulateTwoPhaseDispatches(abstractEvents) {
forEachAccumulated(abstractEvents, accumulateTwoPhaseDispatchesSingle);
}
function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
if (abstractEvent && abstractEvent.type.phasedRegistrationNames) {
// 獲得 phasedRegistrationNames 物件
injection.InstanceHandle.traverseTwoPhase(
abstractEvent.abstractTargetID,
accumulateDirectionalDispatches,
abstractEvent
);
}
}
function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
injection.InstanceHandle.traverseEnterLeave(
fromID,
toID,
accumulateDispatches,
leave,
enter
);
}
function listenerAtPhase(id, abstractEvent, propagationPhase) {
// 獲取對應的註冊名稱
var registrationName =
abstractEvent.type.phasedRegistrationNames[propagationPhase];
// 這個 getListener 函式需要看到 CallbackRegistry.js 相關的方法
return getListener(id, registrationName);
}
function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
// domID = 當前 traverseParentPath 的 ID
// upwards = true / false
var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
// 獲得對應的 cb
var listener = listenerAtPhase(domID, abstractEvent, phase);
if (listener) {
// 如果回撥存在的情況下對 _dispatchListeners 和 _dispatchIDs 進行合併
// 本身不存在的情況下,返回新的 listener 和 domID
// 存在的情況下返回 listener[] 和 domID[]
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, domID);
}
}
// 也是同樣
function accumulateDispatches(id, ignoredDirection, abstractEvent) {
if (abstractEvent && abstractEvent.type.registrationName) {
var listener = getListener(id, abstractEvent.type.registrationName);
if (listener) {
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, id);
}
}
}
複製程式碼
// event/CallbackRegistry.js
// 回撥監聽銀行,用於儲存各種 cb
var listenerBank = {};
var CallbackRegistry = {
// 加入 cb
putListener: function(id, registrationName, listener) {
var bankForRegistrationName =
listenerBank[registrationName] || (listenerBank[registrationName] = {});
bankForRegistrationName[id] = listener;
},
// 獲得 cb
getListener: function(id, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
return bankForRegistrationName && bankForRegistrationName[id];
},
// 移除 cb
deleteListener: function(id, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
if (bankForRegistrationName) {
delete bankForRegistrationName[id];
}
},
// 清空所有
__purge: function() {
listenerBank = {};
}
};
複製程式碼
這裡所呼叫的 injection.InstanceHandle
下的方法其實就是 ReactInstanceHandles
的方法,回憶到在預設注入的時候,那麼這裡來看下:
// core/ReactInstanceHandles.js
var ReactInstanceHandles = {
traverseTwoPhase: function(targetID, cb, arg) {
if (targetID) {
// 直接看到 traverseParentPath 函式
// 比如:
// .reactRoot[0].[3].0
// 捕獲階段: .reactRoot[0] => .reactRoot[0].[3] => .reactRoot[0].[3].0
// 冒泡階段: .reactRoot[0].[3].0 => .reactRoot[0].[3] => .reactRoot[0]
// 他裡面是通過前後 '.' 來控制判斷的
// 具體就是那行 for 迴圈程式碼以及相關的操作函式
traverseParentPath('', targetID, cb, arg, true, false);
traverseParentPath(targetID, '', cb, arg, false, true);
}
},
traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
// 獲取兩個ID最近的共同祖先ID
var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID(
leaveID,
enterID
);
if (longestCommonID !== leaveID) {
traverseParentPath(leaveID, longestCommonID, cb, upArg, false, true);
}
if (longestCommonID !== enterID) {
traverseParentPath(longestCommonID, enterID, cb, downArg, true, false);
}
},
};
// 在兩個ID之間遍歷父路徑(向上或向下)。ID不能相同,並且它們之間必須存在父路徑。
// 方法的作用因為比較抽象,我會舉個例子在上面。
function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
start = start || '';
stop = stop || '';
invariant(
start !== stop,
'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
start
);
var ancestorID = ReactInstanceHandles.getFirstCommonAncestorID(start, stop);
var traverseUp = ancestorID === stop;
invariant(
traverseUp || ancestorID === start,
'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
'not have a parent path.',
start,
stop
);
// Traverse from `start` to `stop` one depth at a time.
var depth = 0;
// 通過 traverseUp 來控制是使用 parentID 函式還是 nextDescendantID 方法。
// 就是從前往後取或者是從後往前取
var traverse = traverseUp ? parentID : ReactInstanceHandles.nextDescendantID;
for (var id = start; /* until break */; id = traverse(id, stop)) {
if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
cb(id, traverseUp, arg);
}
if (id === stop) {
// Only break //after// visiting `stop`.
break;
}
invariant(
depth++ < MAX_TREE_DEPTH,
'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
start, stop
);
}
}
複製程式碼
最後呢就是在 ReactNativeComponent.js
中 _createOpenTagMarkup
方法中的 putListener
函式,看過 putListener
函式就是用於存入回撥函式的,在 getListener
獲得則會去執行。
那麼到此為止,實現事件機制。