前言
本篇文章为上文<一看就懂的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里。