React 原始碼學習(五):事件機制

Zongzi發表於2019-04-05

閱讀原始碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪個版本的時候,我想先接觸到原始碼早期的思想可能會更輕鬆一些,最終我選擇閱讀 0.3-stable 。 那麼接下來,我將從幾個方面來解讀這個版本的原始碼。

  1. React 原始碼學習(一):HTML 元素渲染
  2. React 原始碼學習(二):HTML 子元素渲染
  3. React 原始碼學習(三):CSS 樣式及 DOM 屬性
  4. React 原始碼學習(四):事務機制
  5. React 原始碼學習(五):事件機制
  6. React 原始碼學習(六):元件渲染
  7. React 原始碼學習(七):生命週期
  8. React 原始碼學習(八):元件更新

我們先來看到整體邏輯圖:

事件機制

React 採用將事件掛載至 document 或者 window 上來實現頂級事件。接下來我們會一一來介紹事件的實現過程。

我們來回憶下, ReactNativeComponent.js_createOpenTagMarkup 方法中的 putListener 函式和 ReactMount.jsrenderComponent 方法中的 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 下的 InstanceHandleReactInstanceHandles 在後續事件相關中都會使用到。我們先來看看 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` 的存在。
}
複製程式碼

比較抽象對吧,因為這裡操作的都是事件外掛的預設順序列表中的物件,我們接下來看到 SimpleEventPluginEnterLeaveEventPlugin 來做相應的解釋:

// 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.jsrenderComponent 方法中的 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); 。這裡呼叫的又是另一個模組 EventListenerEventListener 則的對原生瀏覽器 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.accumulateTwoPhaseDispatchesEventPropagators.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 獲得則會去執行。

那麼到此為止,實現事件機制。

相關文章