揭祕React造成合成事件的過程

前言

本篇文章爲上文<一看就懂的React事件機制>附帶的小知識segmentfault

合成事件

圖片描述

圖片描述

EventPluginHub在初始化的時候,注入了七個plugin, 它們是DefaultEventPluginOrder.js裏的api

var DefaultEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'TapEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];

其中咱們最經常使用到的就是SimpleEventPlugin。因此這裏用SimpleEventPlugin來分析。數組

SimpleEventPlugin

// 一開始先生成dispatchConfig,註釋也寫的比較清楚了
/**
 * Turns
 * ['abort', ...]
 * into
 * eventTypes = {
 *   'abort': {
 *     phasedRegistrationNames: {
 *       bubbled: 'onAbort',
 *       captured: 'onAbortCapture',
 *     },
 *     dependencies: ['topAbort'],
 *   },
 *   ...
 * };
 * topLevelEventsToDispatchConfig = {
 *   'topAbort': { sameConfig }
 * };
 */
var eventTypes = {};
var topLevelEventsToDispatchConfig = {};
['abort', 'animationEnd', 'animationIteration', 'animationStart', 'blur', 'canPlay', 'canPlayThrough', 'click', 'contextMenu', 'copy', 'cut', 'doubleClick', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'focus', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'progress', 'rateChange', 'reset', 'scroll', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchMove', 'touchStart', 'transitionEnd', 'volumeChange', 'waiting', 'wheel'].forEach(function (event) {
  var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
  var onEvent = 'on' + capitalizedEvent;
  var topEvent = 'top' + capitalizedEvent;

  var type = {
    phasedRegistrationNames: {
      bubbled: onEvent,
      captured: onEvent + 'Capture'
    },
    dependencies: [topEvent]
  };
  eventTypes[event] = type;
  topLevelEventsToDispatchConfig[topEvent] = type;
});

// 重點是extractEvents函數,用它生成一個合成事件,每一個plugin都必定要有這個函數
var SimpleEventPlugin = { 
  ...,
  extractEvents:function (topLevelType, targetInst, nativeEvent, nativeEventTarget) { 
    var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
    if (!dispatchConfig) {
      return null;
    }
    var EventConstructor;
    switch (topLevelType) {
      ...
      case 'topClick':
        // Firefox creates a click event on right mouse clicks. This removes the
        // unwanted click events.
        if (nativeEvent.button === 2) {
          return null;
        }
      /* falls through */
      case 'topDoubleClick':
      case 'topMouseDown':
      case 'topMouseMove':
      case 'topMouseUp':
      // TODO: Disabled elements should not respond to mouse events
      /* falls through */
      case 'topMouseOut':
      case 'topMouseOver':
      case 'topContextMenu':
         // 有這裏能夠看到onClick使用的構造函數是SyntheticMouseEvent
        EventConstructor = SyntheticMouseEvent;
        break;
      ...
     // 從對象池中取出這個event的一個instance,對象池的概念是爲了節省內存,
     // 這裏不作重點了解,不瞭解的朋友能夠這麼理解,這裏返回了一個 
     // new  EventConstructor()的實例
    var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
    EventPropagators.accumulateTwoPhaseDispatches(event);
    return event;
  } 
}

而後一步步順藤摸瓜函數

EventPropagators.js

function accumulateTwoPhaseDispatches(events) {
  forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}

forEachAccumulated這個功能函數在文章的開頭講過,忘記了朋友能夠回去看看,其實就是當event不是數組的時候,直接調用accumulateTwoPhaseDispatchesSingle,參數爲events。spa

function accumulateTwoPhaseDispatchesSingle(event) {
  if (event && event.dispatchConfig.phasedRegistrationNames) {
    // 這裏有個accumulateDirectionalDispatches放到文章後面講解
    EventPluginUtils.traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
  }
}

EventPluginUtils.js

traverseTwoPhase: function (target, fn, arg) {
    return TreeTraversal.traverseTwoPhase(target, fn, arg);
  },

ReactDomTreeTraversal.js

/**
 * Simulates the traversal of a two-phase, capture/bubble event dispatch.
 */
function traverseTwoPhase(inst, fn, arg) {
  var path = [];
  while (inst) {
    path.push(inst);
    inst = inst._hostParent;
  }
  var i;
  for (i = path.length; i-- > 0;) {
    // 這裏從數組的後面開始循環調用fn,這麼作是捕獲的順序,這樣外層的函數綁定的事件就會被先執行
    fn(path[i], 'captured', arg);
  }
  for (i = 0; i < path.length; i++) {
    // 而後在從數組的前面循環調用,這麼作是冒泡的順序
    fn(path[i], 'bubbled', arg);
  }
}

上文traverseTwoPhase裏的fn其實就是EventPropagator.js 的accumulateDirectionalDispatches,接下來讓咱們看看這個函數作了什麼code

EventPropagator.js

// 這個函數的做用是給合成事件加上listener,最終全部同類型的listener都會放到_dispatchListeners裏,
function accumulateDirectionalDispatches(inst, phase, event) {
  if (process.env.NODE_ENV !== 'production') {
    process.env.NODE_ENV !== 'production' ? warning(inst, 'Dispatching inst must not be null') : void 0;
  }
  // 根據事件階段的不一樣取出響應的事件
  var listener = listenerAtPhase(inst, event, phase);
  if (listener) {
    // accumulateInto在文章的最開始講過,這裏將全部的listener都存入_dispatchListeners中
    // 本文中_dispatchListeners = [onClick, outClick]
    event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
  }
}

下面來看看取出響應事件的過程:對象

/**
 * Some event types have a notion of different registration names for different
 * "phases" of propagation. This finds listeners by a given phase.
 */
// 找到不一樣階段(捕獲/冒泡)元素綁定的回調函數 listener 
function listenerAtPhase(inst, event, propagationPhase) {
  var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  return getListener(inst, registrationName);
}

還記得咱們前面在事件註冊的時候,用putListenerlistener存進listenerBank[registrationName][key]麼,這裏的getListener用於取出咱們以前存放的回調函數.blog

EventPluginHub.js

/**
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @return {?function} The stored callback.
   */
  getListener: function (inst, registrationName) {
    // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
    // live here; needs to be moved to a better place soon
    var bankForRegistrationName = listenerBank[registrationName];
    if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
      return null;
    }
    var key = getDictionaryKey(inst);
    return bankForRegistrationName && bankForRegistrationName[key];
  },

以上,就是生成合成事件的過程,這裏有個重中之中就是合成事件收集了一波同類型例如click的回調函數存在了event._dispatchListeners裏。事件

相關文章
相關標籤/搜索