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

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

前言

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

合成事件

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

  1. var DefaultEventPluginOrder = ['ResponderEventPlugin','SimpleEventPlugin','TapEventPlugin','EnterLeaveEventPlugin','ChangeEventPlugin','SelectEventPlugin','BeforeInputEventPlugin'];

其中我们最常用到的就是SimpleEventPlugin。所以这里用SimpleEventPlugin来分析。

SimpleEventPlugin

  1. // 一开始先生成dispatchConfig,注释也写的比较清楚了
  2. /**
  3. * Turns
  4. * ['abort',...]
  5. * into
  6. * eventTypes = {
  7. * 'abort': {
  8. * phasedRegistrationNames: {
  9. * bubbled: 'onAbort',* captured: 'onAbortCapture',* },* dependencies: ['topAbort'],* },* ...
  10. * };
  11. * topLevelEventsToDispatchConfig = {
  12. * 'topAbort': { sameConfig }
  13. * };
  14. */
  15. var eventTypes = {};
  16. var topLevelEventsToDispatchConfig = {};
  17. ['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) {
  18. var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
  19. var onEvent = 'on' + capitalizedEvent;
  20. var topEvent = 'top' + capitalizedEvent;
  21.  
  22. var type = {
  23. phasedRegistrationNames: {
  24. bubbled: onEvent,captured: onEvent + 'Capture'
  25. },dependencies: [topEvent]
  26. };
  27. eventTypes[event] = type;
  28. topLevelEventsToDispatchConfig[topEvent] = type;
  29. });
  30.  
  31. // 重点是extractEvents函数,用它生成一个合成事件,每个plugin都一定要有这个函数
  32. var SimpleEventPlugin = {
  33. ...,extractEvents:function (topLevelType,targetInst,nativeEvent,nativeEventTarget) {
  34. var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
  35. if (!dispatchConfig) {
  36. return null;
  37. }
  38. var EventConstructor;
  39. switch (topLevelType) {
  40. ...
  41. case 'topClick':
  42. // Firefox creates a click event on right mouse clicks. This removes the
  43. // unwanted click events.
  44. if (nativeEvent.button === 2) {
  45. return null;
  46. }
  47. /* falls through */
  48. case 'topDoubleClick':
  49. case 'topMouseDown':
  50. case 'topMouseMove':
  51. case 'topMouseUp':
  52. // TODO: Disabled elements should not respond to mouse events
  53. /* falls through */
  54. case 'topMouSEOut':
  55. case 'topMouSEOver':
  56. case 'topContextMenu':
  57. // 有这里可以看到onClick使用的构造函数是SyntheticMouseEvent
  58. EventConstructor = SyntheticMouseEvent;
  59. break;
  60. ...
  61. // 从对象池中取出这个event的一个instance,对象池的概念是为了节省内存,
  62. // 这里不做重点了解,不了解的朋友可以这么理解,这里返回了一个
  63. // new EventConstructor()的实例
  64. var event = EventConstructor.getPooled(dispatchConfig,nativeEventTarget);
  65. EventPropagators.accumulateTwoPhaseDispatches(event);
  66. return event;
  67. }
  68. }

然后一步步顺藤摸瓜

EventPropagators.js

  1. function accumulateTwoPhaseDispatches(events) {
  2. forEachAccumulated(events,accumulateTwoPhaseDispatchesSingle);
  3. }

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

  1. function accumulateTwoPhaseDispatchesSingle(event) {
  2. if (event && event.dispatchConfig.phasedRegistrationNames) {
  3. // 这里有个accumulateDirectionalDispatches放到文章后面讲解
  4. EventPluginUtils.traverseTwoPhase(event._targetInst,accumulateDirectionalDispatches,event);
  5. }
  6. }

EventPluginUtils.js

  1. traverseTwoPhase: function (target,fn,arg) {
  2. return TreeTraversal.traverseTwoPhase(target,arg);
  3. },

ReactDomTreeTraversal.js

  1. /**
  2. * Simulates the traversal of a two-phase,capture/bubble event dispatch.
  3. */
  4. function traverseTwoPhase(inst,arg) {
  5. var path = [];
  6. while (inst) {
  7. path.push(inst);
  8. inst = inst._hostParent;
  9. }
  10. var i;
  11. for (i = path.length; i-- > 0;) {
  12. // 这里从数组的后面开始循环调用fn,这么做是捕获的顺序,这样外层的函数绑定的事件就会被先执行
  13. fn(path[i],'captured',arg);
  14. }
  15. for (i = 0; i < path.length; i++) {
  16. // 然后在从数组的前面循环调用,这么做是冒泡的顺序
  17. fn(path[i],'bubbled',arg);
  18. }
  19. }

上文traverseTwoPhase里的fn其实就是EventPropagator.js 的accumulateDirectionalDispatches,接下来让我们看看这个函数做了什么

EventPropagator.js

  1. // 这个函数的作用是给合成事件加上listener,最终所有同类型的listener都会放到_dispatchListeners里,function accumulateDirectionalDispatches(inst,phase,event) {
  2. if (process.env.NODE_ENV !== 'production') {
  3. process.env.NODE_ENV !== 'production' ? warning(inst,'Dispatching inst must not be null') : void 0;
  4. }
  5. // 根据事件阶段的不同取出响应的事件
  6. var listener = listenerAtPhase(inst,event,phase);
  7. if (listener) {
  8. // accumulateInto在文章的最开始讲过,这里将所有的listener都存入_dispatchListeners中
  9. // 本文中_dispatchListeners = [onClick,outClick]
  10. event._dispatchListeners = accumulateInto(event._dispatchListeners,listener);
  11. event._dispatchInstances = accumulateInto(event._dispatchInstances,inst);
  12. }
  13. }

下面来看看取出响应事件的过程:

  1. /**
  2. * Some event types have a notion of different registration names for different
  3. * "phases" of propagation. This finds listeners by a given phase.
  4. */
  5. // 找到不同阶段(捕获/冒泡)元素绑定的回调函数 listener
  6. function listenerAtPhase(inst,propagationPhase) {
  7. var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  8. return getListener(inst,registrationName);
  9. }

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

EventPluginHub.js

  1. /**
  2. * @param {object} inst The instance,which is the source of events.
  3. * @param {string} registrationName Name of listener (e.g. `onClick`).
  4. * @return {?function} The stored callback.
  5. */
  6. getListener: function (inst,registrationName) {
  7. // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
  8. // live here; needs to be moved to a better place soon
  9. var bankForRegistrationName = listenerBank[registrationName];
  10. if (shouldPreventMouseEvent(registrationName,inst._currentElement.type,inst._currentElement.props)) {
  11. return null;
  12. }
  13. var key = getDictionaryKey(inst);
  14. return bankForRegistrationName && bankForRegistrationName[key];
  15. },

以上,就是生成合成事件的过程,这里有个重中之中就是合成事件收集了一波同类型例如click的回调函数存在了event._dispatchListeners里。

猜你在找的React相关文章