前言
本篇文章为上文<一看就懂的React事件机制>附带的小知识
合成事件
EventPluginHub
在初始化的时候,注入了七个plugin,它们是DefaultEventPluginOrder.js
里的
var DefaultEventPluginOrder = ['ResponderEventPlugin','SimpleEventPlugin','TapEventPlugin','EnterLeaveEventPlugin','ChangeEventPlugin','SelectEventPlugin','BeforeInputEventPlugin'];
其中我们最常用到的就是SimpleEventPlugin
。所以这里用SimpleEventPlugin
来分析。
SimpleEventPlugin
// 一开始先生成dispatchConfig,注释也写的比较清楚了 /** * Turns * ['abort',...] * into * eventTypes = { * 'abort': { * phasedRegistrationNames: { * bubbled: 'onAbort',* captured: 'onAbortCapture',* },* dependencies: ['topAbort'],* },* ... * }; * topLevelEventsToDispatchConfig = { * 'topAbort': { sameConfig } * }; */ var eventTypes = {}; var topLevelEventsToDispatchConfig = {}; ['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) { var capitalizedEvent = event[0].toUpperCase() + event.slice(1); var onEvent = 'on' + capitalizedEvent; var topEvent = 'top' + capitalizedEvent; var type = { phasedRegistrationNames: { bubbled: onEvent,captured: onEvent + 'Capture' },dependencies: [topEvent] }; eventTypes[event] = type; topLevelEventsToDispatchConfig[topEvent] = type; }); // 重点是extractEvents函数,用它生成一个合成事件,每个plugin都一定要有这个函数 var SimpleEventPlugin = { ...,extractEvents:function (topLevelType,targetInst,nativeEvent,nativeEventTarget) { var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]; if (!dispatchConfig) { return null; } var EventConstructor; switch (topLevelType) { ... case 'topClick': // Firefox creates a click event on right mouse clicks. This removes the // unwanted click events. if (nativeEvent.button === 2) { return null; } /* falls through */ case 'topDoubleClick': case 'topMouseDown': case 'topMouseMove': case 'topMouseUp': // TODO: Disabled elements should not respond to mouse events /* falls through */ case 'topMouSEOut': case 'topMouSEOver': case 'topContextMenu': // 有这里可以看到onClick使用的构造函数是SyntheticMouseEvent EventConstructor = SyntheticMouseEvent; break; ... // 从对象池中取出这个event的一个instance,对象池的概念是为了节省内存, // 这里不做重点了解,不了解的朋友可以这么理解,这里返回了一个 // new EventConstructor()的实例 var event = EventConstructor.getPooled(dispatchConfig,nativeEventTarget); EventPropagators.accumulateTwoPhaseDispatches(event); return event; } }
然后一步步顺藤摸瓜
EventPropagators.js
function accumulateTwoPhaseDispatches(events) { forEachAccumulated(events,accumulateTwoPhaseDispatchesSingle); }
forEachAccumulated
这个功能函数在文章的开头讲过,忘记了朋友可以回去看看,其实就是当event不是数组的时候,直接调用accumulateTwoPhaseDispatchesSingle,参数为events。
function accumulateTwoPhaseDispatchesSingle(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { // 这里有个accumulateDirectionalDispatches放到文章后面讲解 EventPluginUtils.traverseTwoPhase(event._targetInst,accumulateDirectionalDispatches,event); } }
EventPluginUtils.js
traverseTwoPhase: function (target,fn,arg) { return TreeTraversal.traverseTwoPhase(target,arg); },
ReactDomTreeTraversal.js
/** * Simulates the traversal of a two-phase,capture/bubble event dispatch. */ function traverseTwoPhase(inst,arg) { var path = []; while (inst) { path.push(inst); inst = inst._hostParent; } var i; for (i = path.length; i-- > 0;) { // 这里从数组的后面开始循环调用fn,这么做是捕获的顺序,这样外层的函数绑定的事件就会被先执行 fn(path[i],'captured',arg); } for (i = 0; i < path.length; i++) { // 然后在从数组的前面循环调用,这么做是冒泡的顺序 fn(path[i],'bubbled',arg); } }
上文traverseTwoPhase
里的fn
其实就是EventPropagator.js 的accumulateDirectionalDispatches,接下来让我们看看这个函数做了什么
EventPropagator.js
// 这个函数的作用是给合成事件加上listener,最终所有同类型的listener都会放到_dispatchListeners里,function accumulateDirectionalDispatches(inst,phase,event) { if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV !== 'production' ? warning(inst,'Dispatching inst must not be null') : void 0; } // 根据事件阶段的不同取出响应的事件 var listener = listenerAtPhase(inst,event,phase); if (listener) { // accumulateInto在文章的最开始讲过,这里将所有的listener都存入_dispatchListeners中 // 本文中_dispatchListeners = [onClick,outClick] event._dispatchListeners = accumulateInto(event._dispatchListeners,listener); event._dispatchInstances = accumulateInto(event._dispatchInstances,inst); } }
下面来看看取出响应事件的过程:
/** * Some event types have a notion of different registration names for different * "phases" of propagation. This finds listeners by a given phase. */ // 找到不同阶段(捕获/冒泡)元素绑定的回调函数 listener function listenerAtPhase(inst,propagationPhase) { var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase]; return getListener(inst,registrationName); }
还记得我们前面在事件注册的时候,用putListener
把listener
存进listenerBank[registrationName][key]
么,这里的getListener
用于取出我们之前存放的回调函数.
EventPluginHub.js
/** * @param {object} inst The instance,which is the source of events. * @param {string} registrationName Name of listener (e.g. `onClick`). * @return {?function} The stored callback. */ 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]; },
以上,就是生成合成事件的过程,这里有个重中之中就是合成事件收集了一波同类型例如click的回调函数存在了event._dispatchListeners里。
原文链接:https://www.f2er.com/react/301910.html