react分析(三)合成事件系统

    componentDidMount() {
        const p = document.getElementById('p');
        p.addEventListener('click', function () {
            console.log('p click')
        }, false);
    }
    render() {
        return (
            <div id={'p'}>
                <div onClick={() => console.log('c1 click')}>
                    <div onClick={() => console.log('c2 click')}>
                        click
                    </div>
                </div>
            </div>
        )
    }

先来看个好玩的,刚开始自己的理解输出是c2 click,c2 click,p click,理由是didMount的执行是mount后这个时候DOM已经好了,按道理react的事件早就绑定了,也就是原生DOM事件的绑定应该在react事件系统之后。但实际输出是p click,c2 click,c2 click。现在去翻源码,看了确实是按react先收集事件,最后执行再生命周期

事件注册

    _updateDOMProperties: function (lastProps, nextProps, transaction) {
        var propKey;
        var styleName;
        var styleUpdates;
        for (propKey in lastProps) {
        //....暂时还没摸到是干啥子用的
        }
// 开始遍历props值
        for (propKey in nextProps) {
            var nextProp = nextProps[propKey];
            var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
            if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
                continue;
            }
// 如果是style另外一大砣处理
            if (propKey === STYLE) {
            //    .....

            } 
// 判断事件是不是在react事件系统中,registrationNameModules是通过V:\work\react-source\react-dom\lib\ReactDefaultInjection.js
ReactInjection.EventPluginHub.injectEventPluginsByName注入的
else if (registrationNameModules.hasOwnProperty(propKey)) {
// onclick={()=>0}
                if (nextProp) {
                    enqueuePutListener(this, propKey, nextProp, transaction);
// onclick={}
                } else if (lastProp) {
                    deleteListener(this, propKey);
                }
            } else if (isCustomComponent(this._tag, nextProps)) {
                if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
                    DOMPropertyOperations.setValueForAttribute(getNode(this), propKey, nextProp);
                }
            } else if (DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) {
                var node = getNode(this);
                // If we're updating to null or undefined, we should remove the property
                // from the DOM node instead of inadvertently setting to a string. This
                // brings us in line with the same behavior we have on initial render.
                if (nextProp != null) {
                    DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
                } else {
                    DOMPropertyOperations.deleteValueForProperty(node, propKey);
                }
            }
        }
        if (styleUpdates) {
            CSSPropertyOperations.setValueForStyles(getNode(this), styleUpdates, this);
        }
    },
enqueuePutListener完成事件监听
function enqueuePutListener(inst, registrationName, listener, transaction) {
    if (transaction instanceof ReactServerRenderingTransaction) {
        return;
    }
    if (process.env.NODE_ENV !== 'production') {
        // IE8 has no API for event capturing and the `onScroll` event doesn't
        // bubble.
        process.env.NODE_ENV !== 'production' ? warning(registrationName !== 'onScroll' || isEventSupported('scroll', true), "This browser doesn't support the `onScroll` event") : void 0;
    }
    var containerInfo = inst._hostContainerInfo;
    var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
    var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
    listenTo(registrationName, doc);
    transaction.getReactMountReady().enqueue(putListener, {
        inst: inst,
        registrationName: registrationName,
        listener: listener
    });
}
listenTo: function (registrationName, contentDocumentHandle) {
  var mountAt = contentDocumentHandle;
  var isListening = getListeningForDocument(mountAt);
  var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];

  for (var i = 0; i < dependencies.length; i++) {
    var dependency = dependencies[i];
    if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
      if (dependency === 'topWheel') {
        if (isEventSupported('wheel')) {
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'wheel', mountAt);
        } else if (isEventSupported('mousewheel')) {
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'mousewheel', mountAt);
        } else {
          // Firefox needs to capture a different mouse scroll event.
          // @see http://www.quirksmode.org/dom/events/tests/scroll.html
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'DOMMouseScroll', mountAt);
        }
      } else if (dependency === 'topScroll') {
        if (isEventSupported('scroll', true)) {
          ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topScroll', 'scroll', mountAt);
        } else {
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topScroll', 'scroll', ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE);
        }
      } else if (dependency === 'topFocus' || dependency === 'topBlur') {
        if (isEventSupported('focus', true)) {
          ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topFocus', 'focus', mountAt);
          ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topBlur', 'blur', mountAt);
        } else if (isEventSupported('focusin')) {
          // IE has `focusin` and `focusout` events which bubble.
          // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topFocus', 'focusin', mountAt);
          ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topBlur', 'focusout', mountAt);
        }

        // to make sure blur and focus event listeners are only attached once
        isListening.topBlur = true;
        isListening.topFocus = true;
      } else if (topEventMapping.hasOwnProperty(dependency)) {
        ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);
      }

      isListening[dependency] = true;
    }
  }
},
trapCapturedEvent: function (topLevelType, handlerBaseName, element) {
  if (!element) {
    return null;
  }
  return EventListener.capture(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},
listen: function (target, eventType, callback) {
  if (target.addEventListener) {
    target.addEventListener(eventType, callback, false);
    return {
      remove: function () {
        target.removeEventListener(eventType, callback, false);
      }
    };
  } else if (target.attachEvent) {
    target.attachEvent('on' + eventType, callback);
    return {
      remove: function () {
        target.detachEvent('on' + eventType, callback);
      }
    };
  }
},

listenTo这个函数是所有事件注册的入口,但是从代码可以看到只有wheel,scroll,focus,blur等个别事件类型才是原生的捕获阶段trapCapturedEvent,而且注册的dom统一是document   [mark1]

这里完成的事件的监听

putListener完成存储事件回调
putListener: function (inst, registrationName, listener) {
  !(typeof listener === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected %s listener to be a function, instead got type %s', registrationName, typeof listener) : _prodInvariant('94', registrationName, typeof listener) : void 0;

  var key = getDictionaryKey(inst);
  var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
  bankForRegistrationName[key] = listener;

  var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
  if (PluginModule && PluginModule.didPutListener) {
    PluginModule.didPutListener(inst, registrationName, listener);
  }
},

所有的回调都放到变量listenerBank中,key = getDictionaryKey(inst);就获取实例上的inst._rootNodeID,这个rootid是组件mount里维护的一个全局globalIdCounter++,所以最后存入的形式如:listenerBank[‘topClick’][‘ID’] = listener

var getDictionaryKey = function (inst) {
  // Prevents V8 performance issue:
  // https://github.com/facebook/react/pull/7232
  return '.' + inst._rootNodeID;
};

两个地方会用到这个队列

1、事件触发的时候肯定要执行对应的回调,整个get的逻辑对应上面的存储逻辑

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];
},

2、组件unmount或者组件onclick属性被干了的时候react自动删除了,delete listenerBank[registrationName][key];

派发事件

派发从dispatchEvent函数开始开启一个事务,然后进入到handleTopLevelImpl,

function handleTopLevelImpl(bookKeeping) {

  var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);

  var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);

  // Loop through the hierarchy, in case there's any nested components.

  // It's important that we build the array of ancestors before calling any

  // event handlers, because event handlers can modify the DOM, leading to

  // inconsistencies with ReactMount's node cache. See #1105.

  var ancestor = targetInst;

// 这个递归暂时还没摸清楚是搞啥子明堂的

  do {

    bookKeeping.ancestors.push(ancestor);

    ancestor = ancestor && findParent(ancestor);

  } while (ancestor);

  for (var i = 0; i < bookKeeping.ancestors.length; i++) {

    targetInst = bookKeeping.ancestors[i];

    ReactEventListener._handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));

  }

}

最后进入

function runEventQueueInBatch(events) {

  EventPluginHub.enqueueEvents(events);

  EventPluginHub.processEventQueue(false);

}

var ReactEventEmitterMixin = {

  /**

   * Streams a fired top-level event to `EventPluginHub` where plugins have the

   * opportunity to create `ReactEvent`s to be dispatched.

   */

  handleTopLevel: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {

// 这里生成对应的events对象。详细的生成过程还要继续跟下,另外把回调保存到

    var events = EventPluginHub.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);

// 这步就是执行事件回调了,

    runEventQueueInBatch(events);

  }

};

就个函数就是react合成事件的 捕获 OR 冒泡的关键

function traverseTwoPhase(inst, fn, arg) {

  var path = [];

  while (inst) {

    path.push(inst);

    inst = inst._hostParent;

  }

  var i;

// fn就是下面的accumulateDirectionalDispatches

// 这里path保存是先把捕获listener入列 [parentCaptured, childCaptured]

  for (i = path.length; i– > 0;) {

    fn(path[i], 'captured', arg);

  }

// 然后再把冒泡listener入列[parentCaptured, childCaptured, parentBubbled, childBubbled],其实是用队列的形式实现的捕获,而非原生,可以看上面的mark1

  for (i = 0; i < path.length; i++) {

    fn(path[i], 'bubbled', arg);

  }

}

function accumulateDirectionalDispatches(inst, phase, event) {

  if ("development" !== 'production') {

    "development" !== 'production' ? warning(inst, 'Dispatching inst must not be null') : void 0;

  }

  var listener = listenerAtPhase(inst, event, phase);

  if (listener) {

    event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);

    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);

  }

}

最后遍历event._dispatchListeners

function executeDispatchesInOrder(event, simulated) {

// 这里的dispatchListeners 就是上面的[parentCaptured, childCaptured, parentBubbled, childBubbled]

  var dispatchListeners = event._dispatchListeners;

  var dispatchInstances = event._dispatchInstances;

  if ("development" !== 'production') {

    validateEventDispatches(event);

  }

  if (Array.isArray(dispatchListeners)) {

    for (var i = 0; i < dispatchListeners.length; i++) {

// 就个东东就是平时代码里的event.stopPropagation()了,如果执行该函数这里就直接break了,注:只是退出react这套事件规则,原生的该冒泡还是冒泡

      if (event.isPropagationStopped()) {

        break;

      }

      // Listeners and Instances are two parallel arrays that are always in sync.

      executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);

    }

  } else if (dispatchListeners) {

    executeDispatch(event, simulated, dispatchListeners, dispatchInstances);

  }

  event._dispatchListeners = null;

  event._dispatchInstances = null;

}

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注