React事件四--仿制DOM事件流 1

420 阅读4分钟

紧接上文事件分发

从上文我们知道,我们绑定的事件侦听器(就是回调函数比如jsx中onClick中花括号内的函数。)本身是存储在EventPluginHub模块的listenerBank中的。我们在jsx中注册的事件是被document代理的,而document代理的事件的侦听器是React自定义的事件分发方法。如下:

ReactEventListener 模块

 <!--
    这里React为每一我们注册的个事件比如onClick,提供了一个方法,来代替了我们写的回调函数。
 -->
 
  dispatchEvent: function (topLevelType, nativeEvent) {
  
   <!--
     创建了一个对象,这个对象里包含了一个nativeEvent(源事件对象)一个当前的top事件。比如topAbort。
    一个数组 ancestor。用来存储触发元素的祖先层次结构。
    在初始条件下。ancestor是空数组,而且nativeEvent是没有赋值的。
    这个对象的作用就是用来存储的。为每一次的事件触发,存储一些内容。
    
    bookKeeping是 TopLevelCallbackBookKeeping 采用React的池化技术生产的实例。
    实例主要包含内容为
    {
      this.topLevelType = topLevelType; 触发的顶层事件,比如topClick。是原生事件在document上的表现形式。onClick==>topClick
      this.nativeEvent = nativeEvent; 原生事件对象。触发事件的那个元素的event对象。
      this.ancestors = [];用来存储祖先的层次结构
    }
    只有记得这个bookKeeping对象里存了一个触发事件的顶级事件名称,一个触发事件的源元素的event对象,一个空数组就够了。
   -->
   
    var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);

    try {
      <!--
        ReactUpdates.batchedUpdates是 使用ReactDefaultBatchingStrategy模块的批处理策略。
        batchedUpdates
      -->

      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
      
      <!--
        这里 ReactUpdates.batchedUpdates 是采用React的批处理策略,来处理参数。
        主要是使用使用bookKeeping作为 handleTopLevelImpl方法 的参数来调用该方法
      -->
      
    } finally {
    
      TopLevelCallbackBookKeeping.release(bookKeeping);
      
    }
  }

ReactEventListener模块的dispatchEvent方法的核心是调用了ReactUpdates.batchedUpdates 方法。

ReactUpdates模块的 batchedUpdates方法

    'ReactUpdates的batchedUpdates方法是调用了 batchingStrategy 的batchedUpdates方法,如下:'
    
        function batchedUpdates(callback, a, b, c, d, e) {
          ensureInjected();
          batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
        }
    
    '而batchingStrategy 模块是使用了注入机制注入进来的,如下 '
    
        var ReactUpdatesInjection = {
            c'c'c'c: function (_batchingStrategy) {
                batchingStrategy = _batchingStrategy;
            }
        };
        
        
    
    'injectBatchingStrategy 方法是在ReactDefaultInjection模块中通过如下的方式被调用,而后注入了进来ReactDefaultBatchingStrategy模块'
    
    ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);
    
    '看一下注入的 ReactDefaultBatchingStrategy '
    
    var ReactDefaultBatchingStrategy = {
        
        <!--
            这个变量用来表示当期是否处于批处理状态
        -->
        isBatchingUpdates: false,
        
        <!--
            batchedUpdates方法的作用是以给的参数调用了callback。
        -->
        
        batchedUpdates: function (callback, a, b, c, d, e) {
            var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        
            ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        
            if (alreadyBatchingUpdates) {
              callback(a, b, c, d, e);
            } else {
              transaction.perform(callback, null, a, b, c, d, e);
            }
        }
    

如上 ReactUpdates.batchedUpdates方法接收两个参数,一个是函数,一个是参数。而该方法最终的作用是是以传入的参数作为传入的方法的参数,执行这个方法。核心是使用了React的批处理策略。关于React的批处理策略我们以后会说,现在我们继续往下:

handleTopLevelImpl 函数

    'handleTopLevelImpl方法只是简单的调用了handleTopLevelWithoutPath方法'
    
    function handleTopLevelImpl(bookKeeping) {
        handleTopLevelWithoutPath (bookKeeping);
    }
    
    '看一下handleTopLevelWithoutPath方法'
    
    function handleTopLevelWithoutPath(bookKeeping) {
        <!--
            getEventTarget方法作用是根据源事件,找到触发源事件的DOM元素。
            getFirstReactDOM得到距离触发事件的源对象最近的dom,一般是其自身,也考虑到触发事件的可能是text_node
        -->
        var topLevelTarget = ReactMount.getFirstReactDOM(getEventTarget(bookKeeping.nativeEvent)) || window;
        
        <!--
            topLevelTarget是触发事件的当前元素,这里的方法就是为了找到最近的一个DOM元素,
        -->
        <!--
            这里的遍历就是为了防止有嵌套层次。比如。我们有一个根元素是
            <div id= "app"></div>这个元素,我们这个遍历的作用是保证不会再嵌套一个元素 <div id="app1"></div>作为根元素的根元素。担心可能是多页面应用。
            
            因为事件可能会改变DOM所以,这里要存储一下源事件DOM。
        -->
          var ancestor = topLevelTarget;
          while (ancestor) {
            bookKeeping.ancestors.push(ancestor);
            ancestor = findParent(ancestor);
          }
          
        <!--
            ancestors 里一般是存了当前触发事件的元素
        -->

  

          for (var i = 0; i < bookKeeping.ancestors.length; i++) {
            topLevelTarget = bookKeeping.ancestors[i];
            // topLevelTarget 是事件触发的源元素。
            var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
            // topLevelTargetID 源元素的id
        
            ReactEventListener._handleTopLevel(
                bookKeeping.topLevelType, 
                topLevelTarget, 
                topLevelTargetID, 
                bookKeeping.nativeEvent, 
                getEventTarget(bookKeeping.nativeEvent)
            );
        }
    
    

handleTopLevelImpl最终是调用了如下这个方法

    ReactEventListener._handleTopLevel(
        bookKeeping.topLevelType, 
        topLevelTarget, 
        topLevelTargetID, 
        bookKeeping.nativeEvent, 
        getEventTarget(bookKeeping.nativeEvent)
    );
    
    <!--
        bookKeeping.topLevelType:存储document触发的时候的事件类型,比如topAbort。
        topLevelTarget:就是触发事件的原始元素,一般是一个元素,如果触发元素的是一个text_node的话,会找到text_node的父元素,这个参数一定会得到一个dom元素的,他和getEventTarget(bookKeeping.nativeEvent)是一样的。
        topLevelTargetID:原始元素的id
        bookKeeping.nativeEvent:原始事件对象。
        getEventTarget(bookKeeping.nativeEvent):触发事件的那个DOM元素,可能会是text_node。这个返回的是text_node的父元素
        
         
    
    -->

理一遍:我们先使用ReactUpdates.batchedUpdates()批处理策略来处理方法 handleTopLevelImpl,方法handleTopLevelImpl 调用了handleTopLevelWithoutPath方法。

handleTopLevelWithoutPath方法的作用到目前为止是确认不会有嵌套存在,而后调用了如下的方法:

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