React事件机制五--仿制事件流2

503 阅读5分钟

上文仿制事件流1.我们说到了下边的方法。

    ReactEventListener._handleTopLevel(
        bookKeeping.topLevelType, 
        topLevelTarget, 
        topLevelTargetID, 
        bookKeeping.nativeEvent, 
        getEventTarget(bookKeeping.nativeEvent)
    );

先来一段简洁的介绍

  ReactEventListener._handleTopLevel 方法最终是调用了
  ReactEventEmitterMixin.handleTopLevel 方法。
  而ReactEventEmitterMixin.handleTopLevel 方法是调用 EventPluginHub.extractEvents 方法生成合成事件,
  而后将合成事件放入事件队列里执行。

  而关于生成合成事件:
  EventPluginHub.extractEvents 方法会选择不同的事件插件进行处理生成合成事件。
  比如调用 simpleEventPlugin 的 extractEvents 方法,而
  simpleEventPlugin 的 extractEvents 方法的核心是调用了
  EventPropagators.accumulateTwoPhaseDispatches 方法。
  而 EventPropagators.accumulateTwoPhaseDispatches 方法则是调用了
  该模块下的 accumulateTwoPhaseDispatches。
  而 accumulateTwoPhaseDispatches 方法则是调用了
  EventPluginHub.injection.getInstanceHandle().traverseTwoPhase()方法。
  实则是调用了 ReactInstanceHandles模块的 traverseTwoPhase() 方法,
  traverseTwoPhase 方法则是根据给的事件触发的id来执行两个方法。
  ReactInstanceHandles 模块的下的
  traverseParentPath('', targetID, cb, arg, true, false);
  该函数的主要作用是计算从React根组件到触发事件的这个组件之间的所经过的全部React组件
  而后主要是调用了一个方法。EventPropagators模块下的 accumulateDirectionalDispatches 方法。
  而 EventPropagators.accumulateDirectionalDispatches 方法的主要作用是 根据捕获事件流所经过的全部的
  React组件的id找到对应事件的侦听器。主要是从 listenerBank 中取出来,而后放入源事件的属性中。
  如下:
  event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
  event._dispatchIDs = accumulateInto(event._dispatchIDs, domID);

  traverseParentPath(targetID, '', cb, arg, false, true);
  这个方法只是冒泡流的机制来找到所有的侦听器。

  最后将合成事件放入队列中,而后一个个的去执行这些事件。
  用到的方法是。
  入队
  EventPluginHub.enqueueEvents(events);
  执行队列
  EventPluginHub.processEventQueue(false);

精华以上,糟粕其下。接下来我们一个个的说,

ReactEventListener._handleTopLevel方法是调用了下边的这个方法。

    var ReactEventEmitterMixin = {
            <!--
              先看看参数:
              topLevelType:就是触发的事件类型在document上的表示 onClick表示为topClick。
              topLevelTarget:触发事件的源元素(nodeType为1的元素。如果是text_node触发的事件,那就向上找到它的父元素作为触发事件的源元素,Safari中会有这种情况)
              topLevelTargetID:触发事件的元素的id
              nativeEvent:触发事件的源事件对象。
              nativeEventTarget:和topLevelTarget应该是一个东西
            -->
            
          handleTopLevel: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
          
            <!--
                首先是调用了 EventPluginHub.extractEvents来生成合成事件。
            -->
            
            var events = EventPluginHub.extractEvents(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget);
            
            <!--
                将合成的事件放入队列里,而后执行。
            -->
            
            runEventQueueInBatch(events);
            
            <!--
                runEventQueueInBatch方法如下。
                function runEventQueueInBatch(events) {
                    EventPluginHub.enqueueEvents(events);
                    EventPluginHub.processEventQueue(false);
                }
            -->
            
          }
        };
到了这里我们将合成事件放入了一个事件队列里,而后执行这个队列。但是,没有接触到关于事件流的情形。如果一个元素有一个click事件,其父元素也有click事件,那么React是如何在元素事件触发的时候也触发其父元素的click事件。

handleTopLevel方法的核心是调用了EventPluginHub模块的extractEvents方法,我们来看一下EventPluginHub.extractEvents()方法。

EventPluginHub.extractEvents()方法。

    extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
        var events;
        var plugins = EventPluginRegistry.plugins;
        for (var i = 0; i < plugins.length; i++) {
        
          var possiblePlugin = plugins[i];
          
          <!--
            plugins内的内容为默认的事件插件,用来处理原生事件为合成事件,这些插件也是通过注入机制注入进来的
            [
                SimpleEventPlugin: SimpleEventPlugin,
                EnterLeaveEventPlugin: EnterLeaveEventPlugin,
                ChangeEventPlugin: ChangeEventPlugin,
                SelectEventPlugin: SelectEventPlugin,
                BeforeInputEventPlugin: BeforeInputEventPlugin
            ]
          -->
          
          if (possiblePlugin) {
            // 使用提供的事件插件来提取合成事件
            var extractedEvents = possiblePlugin.extractEvents(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget);
            if (extractedEvents) {
              events = accumulateInto(events, extractedEvents);
            }
          }
        }
        return events;
  },

我们以simpleEventPlugin为例来说一下事件插件:

extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
    var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
    <!--
        dispatchConfig如下:
        {
            dependencies: ["topClick"]
            phasedRegistrationNames:{
                bubbled: "onClick"
                captured: "onClickCapture"
            }
      }
    -->
    <!--
        我们假设现在触发的事件是click事件,topLevelType为topClick。React会根据事件类型选择事件插件
    -->
    switch (topLevelType) {
        case topLevelTypes.topClick:
            EventConstructor = SyntheticMouseEvent;
            break;    
    }
    <!--
        使用池化技术来使用EventConstructor生成一个event。
        而EventConstructor则是 SyntheticMouseEvent 类(叫做鼠标合成事件插件)
    -->
    var event = EventConstructor.getPooled(dispatchConfig, topLevelTargetID, nativeEvent, nativeEventTarget); -----------(1)
    <!--
        这个方法就是用来收集捕获事件流和冒泡事件流的事件。
    -->
    EventPropagators.accumulateTwoPhaseDispatches(event);--------(2)
    return event;
  },
这里提一句,合成事件的作用主要是为了抹平不同的浏览器之间的差异,同样的click事件在Chrome,Safari,Opera,IE等的浏览器中的表现是不一样的,而React提供了一个中间层 SyntheticEvent来将事件处理为统一样子,比如提供一个统一的阻止默认事件的函数,或者是阻止冒泡的函数等,如此不同的浏览器事件经过处理定义为拥有同样的属性的对象,最后会将原生事件对象也放在其中,这就形成了一个合成事件。React对于一个事件会使用默认的五个事件插件来进行处理,最终会将其合成为一个合成事件的数组。

我们一个个来看核心代码,首先是 (1):

    <!--
         首先我们假设是topClick事件,那么EventConstructor就是 SyntheticMouseEvent。
         SyntheticMouseEvent 是主要用来处理鼠标事件的事件插件。
         下边的代码的作用是启用池化技术初始化一个 SyntheticMouseEvent 实例。我们先不管池化技术,只简单的认为是初始化了一个 SyntheticMouseEvent 实例,
    -->
     var event = EventConstructor.getPooled(dispatchConfig, topLevelTargetID, nativeEvent, nativeEventTarget);
     
     分割线
     ==========================================================
     
     来看一下 SyntheticMouseEvent 类。
     
     <!--
        如下是 SyntheticMouseEvent 抹平不同的浏览器的鼠标事件所做的,定义了一些属性。
        MouseEventInterface是一个接口 Interface。
     -->
     var MouseEventInterface = {
          screenX: null,
          screenY: null,
          clientX: null,
          clientY: null,
          ctrlKey: null,
          shiftKey: null,
          altKey: null,
          metaKey: null,
          getModifierState: getEventModifierState,
          button: function (event) {
            // Webkit, Firefox, IE9+
            // which:  1 2 3
            // button: 0 1 2 (standard)
            var button = event.button;
            if ('which' in event) {
              return button;
            }
            // IE<9
            // which:  undefined
            // button: 0 0 0
            // button: 1 4 2 (onmouseup)
            return button === 2 ? 2 : button === 4 ? 1 : 0;
          },
          buttons: null,
          relatedTarget: function (event) {
            return event.relatedTarget || (event.fromElement === event.srcElement ? event.toElement : event.fromElement);
          },
          // "Proprietary" Interface.
          pageX: function (event) {
            return 'pageX' in event ? event.pageX : event.clientX + ViewportMetrics.currentScrollLeft;
          },
          pageY: function (event) {
            return 'pageY' in event ? event.pageY : event.clientY + ViewportMetrics.currentScrollTop;
          }
        };
        
        
    'ok 到此,我们便先打住,以后再来探究React是如何生成合成事件的。'
    

继续看核心代码其 (2):

    <!--
        这里就是收集事件的传播过程。捕获事件流和冒泡事件流。
    -->
    EventPropagators.accumulateTwoPhaseDispatches(event);

先看看 EventPropagators 模块:

    <!--
            EventPropagators.accumulateTwoPhaseDispatches方法是调用了如下方法。
            forEachAccumulated方法的作用是判断第一个参数如果是数组
            那就循环执行第二个参数(这个参数是方法)
            如果不是函数,直接就以第一个参数作为第二个参数的参数,执行第二个参树。
            总之就是下边的方法会执行第二个参数。
    -->
    
    function accumulateTwoPhaseDispatches(events) {
        forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
    }
    
    分割线
    ==========================================================
    <!--
        accumulateTwoPhaseDispatchesSingle方法如下。
    -->
    function accumulateTwoPhaseDispatchesSingle(event) {
          if (event && event.dispatchConfig.phasedRegistrationNames) {
            EventPluginHub.injection.getInstanceHandle().traverseTwoPhase(event.dispatchMarker, accumulateDirectionalDispatches, event);
          }
    }
    

说了这么多,也没有说到正点上,EventPluginHub.injection.getInstanceHandle().traverseTwoPhase()方法是仿制事件流的核心。限于篇幅,我们另起一篇。