React源码分析 – 事件机制

React源码分析 – 事件机制React的事件机制还是很好玩的,其中模拟事件传递和利用document委托大部分事件的想法比较有意思。 _updateDOMProperties是事件参数处理的入口,只要注意enqueuePutListener这个方法就好了,这是注册事件的入口函数。registrationN…

React的事件机制还是很好玩的,其中模拟事件传递和利用document委托大部分事件的想法比较有意思。

事件机制流程图

event-react

代码分析

(代码仅包含涉及事件参数的部分)

_updateDOMProperties是事件参数处理的入口,只要注意enqueuePutListener这个方法就好了,这是注册事件的入口函数。registrationNameModules变量保存事件类型和对应的方法的映射的一个对象,如图:

registrationnamemodules

这些映射的初始化的地方在《React源码分析 – 组件初次渲染》解释过了。

_updateDOMProperties: function (lastProps, nextProps, transaction) {
  var propKey;
  var styleName;
  var styleUpdates;
  for (propKey in lastProps) {
    if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
      continue;
    }
    if (registrationNameModules.hasOwnProperty(propKey)) {
      if (lastProps[propKey]) {
        // Only call deleteListener if there was a listener previously or
        // else willDeleteListener gets called when there wasn't actually a
        // listener (e.g., onClick={null})
        deleteListener(this, propKey);
      }
    }
  }
  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;
    }
    if (registrationNameModules.hasOwnProperty(propKey)) { // 处理事件参数。
      if (nextProp) {
        enqueuePutListener(this, propKey, nextProp, transaction); // 注册事件,委托到属于的document上
      } else if (lastProp) {
        deleteListener(this, propKey);
      }
    }
  }
}

enqueuePutListener

  • listenTo
  • putListener
function enqueuePutListener(inst, registrationName, listener, transaction) {
  var containerInfo = inst._nativeContainerInfo;
  var doc = containerInfo._ownerDocument; // 大部分的事件都被到对应的document上
  if (!doc) { // ssr
    // Server rendering.
    return;
  }
  listenTo(registrationName, doc);
  transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener
  });
}

listenTo是将事件委托到document的方法,大部分事件是委托到document上的。但是因为document上能够catch的事件类型的限制(Document Object Model Events),不是所有的事件类型都委托到document,少部分是直接委托到元素本身上的。

putListener将对应的类型的事件、事件的目标对象和事件触发时执行的方法添加到listenerBank对象中。

listenTo: function (registrationName, contentDocumentHandle) {
  var mountAt = contentDocumentHandle;
  var isListening = getListeningForDocument(mountAt);
  var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];

  var topLevelTypes = EventConstants.topLevelTypes;
  for (var i = 0; i < dependencies.length; i++) {
    var dependency = dependencies[i];
    if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
      // 先判断先几个需要特殊处理的事件,主要都是兼容性的原因。
      if (...) {
        ......
      } else if (topEventMapping.hasOwnProperty(dependency)) {
        ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);
      }
      isListening[dependency] = true;
    }
  }
}

// 冒泡阶段的触发的事件的委托
trapBubbledEvent: function (topLevelType, handlerBaseName, handle) {
  return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelType, handlerBaseName, handle);
},

// 捕获阶段的触发的事件的委托
trapCapturedEvent: function (topLevelType, handlerBaseName, handle) {
  return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelType, handlerBaseName, handle);
},

trapBubbledEvent: function (topLevelType, handlerBaseName, handle) {
  return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},

trapCapturedEvent: function (topLevelType, handlerBaseName, handle) {
  return EventListener.capture(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},

listen: function listen(target, eventType, callback) {
  if (target.addEventListener) {
    target.addEventListener(eventType, callback, false);
  }
},

capture: function capture(target, eventType, callback) {
  if (target.addEventListener) {
    target.addEventListener(eventType, callback, true);
  }
},

重点在于所有的委托的事件的回调函数都是ReactEventListener.dispatchEvent。

dispatchEvent: function (topLevelType, nativeEvent) {
  // bookKeeping的初始化使用了react在源码中用到的对象池的方法来避免多余的垃圾回收。
  // bookKeeping的作用看ta的定义就知道了,就是一个用来保存过程中会使用到的变量的对象。
  var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
  try {
    ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
  } finally {
    TopLevelCallbackBookKeeping.release(bookKeeping);
  }
}

handleTopLevelImpl方法遍历事件触发对象以及其的父级元素(事件传递),对每个元素执行_handleTopLevel方法。

function handleTopLevelImpl(bookKeeping) {
  var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);
  var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);
  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));
  }
}

handleTopLevel根据事件对象以及触发的事件类型提取出所有需要被执行的事件以及对应的回调函数,统一由runEventQueueInBatch执行。

handleTopLevel: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var events = EventPluginHub.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
  runEventQueueInBatch(events);
}

extractEvents方法调用了对应的plugin的extractEvents方法来获取对应的plugin类型的需要执行的事件,然后accumulateInto到一起。

extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
   var events;
   var plugins = EventPluginRegistry.plugins;
   for (var i = 0; i < plugins.length; i++) {
     // Not every plugin in the ordering may be loaded at runtime.
     var possiblePlugin = plugins[i];
     if (possiblePlugin) {
       var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
       if (extractedEvents) {
         events = accumulateInto(events, extractedEvents);
       }
     }
   }
   return events;
 }

plugin的extractEvents方法中的有意思的地方在于 EventPropagators.accumulateTwoPhaseDispatches(event)

EventPropagators.accumulateTwoPhaseDispatches中模拟了事件传递的过程即:capture -> target -> bubble 的过程,将这个路径上的所有的符合事件类型的回调函数以及对应的元素按照事件传递的顺序返回。

React源码分析 - 事件机制

(图片来自Event dispatch and DOM event flow

function traverseTwoPhase(inst, fn, arg) {
  var path = [];
  while (inst) {
    path.push(inst);
    inst = inst._nativeParent;
  }
  var i;
  for (i = path.length; i-- > 0;) {
    fn(path[i], false, arg);
  }
  for (i = 0; i < path.length; i++) {
    fn(path[i], true, arg);
  }
}

traverseTwoPhase方法模拟了事件传递的过程并且获取对应的回调函数和事件对象保存在react合成的event对象的_dispatchListeners和_dispatchInstances上

function accumulateDirectionalDispatches(inst, upwards, event) {
  var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
  var listener = listenerAtPhase(inst, event, phase);
  if (listener) {
    // event._dispatchListeners结果就是这个event在event flow的过程中会触发那些listenter的callback【按照event flow的顺序push到一个数组中了】
    event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
  }
}

查询listener和对应的inst使用的是事件的类型以及_rootNodeID,listenerBank中保存了对应一个类型下元素的回调函数:

listenerBank

function listenerAtPhase(inst, event, propagationPhase) {
  var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  return getListener(inst, registrationName);
}

getListener: function (inst, registrationName) {
    var bankForRegistrationName = listenerBank[registrationName];
    return bankForRegistrationName && bankForRegistrationName[inst._rootNodeID];
  },

对于listenerBank内容的生成由之前说的第二个主要方法putListener完成。

putListener 使用事务的方式统一在ReactMountReady阶段执行。

putListener: function (inst, registrationName, listener) {
  var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
  bankForRegistrationName[inst._rootNodeID] = listener;
}

在extractEvents了对应触发的事件类型的events后通过runEventQueueInBatch(events)将所有的合成事件放到事件队列里面,第二步是逐个执行

function runEventQueueInBatch(events) {
  EventPluginHub.enqueueEvents(events);
  EventPluginHub.processEventQueue(false);
}

function executeDispatchesInOrder(event, simulated) {
  var dispatchListeners = event._dispatchListeners;
  var dispatchInstances = event._dispatchInstances;
  if (process.env.NODE_ENV !== 'production') {
    validateEventDispatches(event);
  }
  if (Array.isArray(dispatchListeners)) {
    for (var i = 0; i < dispatchListeners.length; i++) {
      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;
}

function executeDispatch(event, simulated, listener, inst) {
  var type = event.type || 'unknown-event';
  event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
  if (simulated) {
    ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
  } else {
    ReactErrorUtils.invokeGuardedCallback(type, listener, event);
  }
  event.currentTarget = null;
}

function invokeGuardedCallback(name, func, a, b) {
  try {
    return func(a, b);
  } catch (x) {
    if (caughtError === null) {
      caughtError = x;
    }
    return undefined;
  }
}

总结

  • 统一的分发函数dispatchEvent。
  • React的事件对象是合成对象(SyntheticEvent)。
  • 几乎所有的事件都委托到document,达到性能优化的目的。
  • 合成事件与原生事件混用要注意React的事件基本都是委托到document。

参考资料

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13092.html

(0)

相关推荐

发表回复

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