揭秘React形成合成事件的过程

前端之家收集整理的这篇文章主要介绍了揭秘React形成合成事件的过程前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

本篇文章为上文<一看就懂的React事件机制>附带的小知识

合成事件

EventPluginHub在初始化的时候,注入了七个plugin,它们是DefaultEventPluginOrder.js里的

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,nativeEventTarget);
    EventPropagators.accumulateTwoPhaseDispatches(event);
    return event;
  } 
}

然后一步步顺藤摸瓜

EventPropagators.js

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

forEachAccumulated这个功能函数文章的开头讲过,忘记了朋友可以回去看看,其实就是当event不是数组的时候,直接调用accumulateTwoPhaseDispatchesSingle,参数为events。

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,arg);
  },

ReactDomTreeTraversal.js

/**
 * Simulates the traversal of a two-phase,capture/bubble event dispatch.
 */
function traverseTwoPhase(inst,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,接下来让我们看看这个函数做了什么

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,propagationPhase) {
  var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  return getListener(inst,registrationName);
}

还记得我们前面在事件注册的时候,用putListenerlistener存进listenerBank[registrationName][key]么,这里的getListener用于取出我们之前存放的回调函数.

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里。

原文链接:https://www.f2er.com/react/301910.html

猜你在找的React相关文章