挺久没有写博客了,最近一直在研究React的源码,对整个流程有了大概的了解,这里先总结下React事件系统的实现,之后还会对Fiber架构有个总结。
DOM原生事件 1 2 3 <div > <button > click</button > </div >
我们知道,当点击button时,会生成一个事件对象,经捕获(capture)document -> div -> button,冒泡(bubble)button -> div -> document,如果路径上的元素在对应阶段注册了回调,则执行回调,其中可能会preventDefault或stopPropagation,这个过程需要兼容不同的浏览器。
如果每个元素都注册回调,对性能的影响会很大,而解决的方法就是事件委托(event delegation)。
React事件系统主要解决的就是兼容问题和事件委托。
合成事件 出于兼容性考虑,React有自己实现的合成事件(SyntheticEvent),这也是React事件系统的基础。
重要属性 nativeEvent: 对应的原生事件
_targetInst: target对应的fiber
dispatchConfig: 该topLevel(topClick)对应各阶段的属性(onClick, onClickCaptured)
_dispatchListeners: 按事件流抓取的回调序列
_dispatchInstances: 回调对应的fiber序列
扩展性 DOM的事件对象中,根据事件类型,属性中有部分是共有的,比如target、type;其他是特有的,如MouseEvent的clientX等。为了可扩展性,React实现了类的Interface,当实例化的时候,会根据Interface把nativeEvent对应的属性赋值到实例里,并提供了extend方法来扩展Interface,实现继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 SyntheticEvent.Interface = { type: null , defaultPrevented: null , } function SyntheticEvent (dispatchConfig, targetInst, nativeEvent, nativeEventTarget ) { const Interface = this .constructor.Interface; for (const propName in Interface) { this [propName] = nativeEvent[propName]; } } SyntheticEvent.extend = function (Interface ) { const Super = this ; const E = function ( ) {}; E.prototype = Super.prototype; const prototype = new E(); function Class ( ) { return Super.apply(this , arguments ); } Object .assign(prototype, Class.prototype); Class.prototype = prototype; Class.prototype.constructor = Class; Class.Interface = Object .assign({}, Super.Interface, Interface); Class.extend = Super.extend; return Class; }; SyntheticUIEvent = SyntheticEvent.extend({ view: null , }) SyntheticMouseEvent = SyntheticUIEvent.extend({ clientX: null , })
原型方法 实现了兼容的preventDefault和stopPropagation,通过自定义的isPropagationStopped决定是否执行之后的回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Object .assign(SyntheticEvent.prototype, { preventDefault: function ( ) { this .defaultPrevented = true ; const event = this .nativeEvent; if (!event) { return ; } if (event.preventDefault) { event.preventDefault(); } else if (typeof event.returnValue !== 'unknown' ) { event.returnValue = false ; } this .isDefaultPrevented = emptyFunction.thatReturnsTrue; }, stopPropagation: function ( ) { const event = this .nativeEvent; if (!event) { return ; } if (event.stopPropagation) { event.stopPropagation(); } else if (typeof event.cancelBubble !== 'unknown' ) { event.cancelBubble = true ; } this .isPropagationStopped = emptyFunction.thatReturnsTrue; },
对象池 为了减少GC负担,合成事件使用了对象池,新建对象时会从对象池中取实例再赋值,使用后属性都重置再放入池中。
注册事件 React中的事件回调不绑定在实际的DOM元素中,我们知道React提供了更高的一层Fiber抽象,而回调就包含在Fiber的props中(如{onClick: f()}
)。当在根据Fiber初始化DOM元素(setInitialDOMProperties)时,如果发现有prop为onClick,则会将dispatchEvent.bind(null, "topClick")
注册到document,使document开始监听click事件。
需要注意的是,document只起监听分发的作用,本身与回调是解耦的。监听到click事件后,在dispatchEvent
的时候才开始抓取对应的回调,回调始终与DOM元素抽象出的fiber绑定,这样fiber消失时,回调也会消失。这也符合React的设计原则,即在消耗大的DOM元素上抽象出轻量化的fiber,以提高最终的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function trapBubbledEvent (topLevelType, baseName, element ) { if (!element) return EventListener.listen(element, baseName, dispatchEvent.bind(null , topLevelType)) } var EventListener = { listen(target, eventType, callback) { if (target.addEventListener) { target.addEventListener(eventType, callback, false ) return { remove() { target.removeEventListener(eventType, callback, false ) } } } else if (target.attachEvent) { target.attachEvent("on" + eventType, callback) return { remove() { target.detachEvent("on" + eventType, callback) } } } }, }
抓取事件 当document在冒泡阶段拿到nativeEvent,实际上事件流已经接近结束了。而React模拟DOM实现了自己的事件流来抓取事件并依次执行回调。
针对不同的事件,React有不同的plugin来得到回调。最普遍的SimplePlugin先得到从target到React容器的路径中DOM元素对应的fiber序列,再模拟捕获、冒泡进行两次遍历,分别寻找onClickCaptured, onClick属性,将得到的回调放入合成事件的_dispatchListeners中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const SimpleEventPlugin = { extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) { var dispatchConfig = topLevelTypeToDispatchConfig[topLevelType] if (!dispatchConfig) return null var EventConstructor switch (topLevelType) { case "topClick" : EventConstructor = SyntheticMouseEvent break ; default : break ; } var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) traverseTwoPhase(targetInst, event) return event } } function traverseTwoPhase (inst, event ) { var path = [] while (inst) { if (inst.tag === HostComponent) path.push(inst) inst = inst.return } var i, len = path.length for (i = len - 1 ; i >= 0 ; i--) { accumulateDispatches(path[i], event, "captured" ) } for (i = 0 ; i < len; i++) { accumulateDispatches(path[i], event, "bubbled" ) } } function accumulateDispatches (inst, event, phase ) { var registrationName = event.dispatchConfig.phasedRegistrationNames[phase] var listener = getListener(inst, registrationName) if (listener) { event._dispatchListeners = accumulateInto(event._dispatchListeners, listener) event._dispatchInstances = accumulateInto(event._dispatchInstances, inst) } }
执行回调 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function executeDispatchesInOrder (event, simulated ) { const dispatchListeners = event._dispatchListeners; const dispatchInstances = event._dispatchInstances; if (Array .isArray(dispatchListeners)) { for (let i = 0 ; i < dispatchListeners.length; i++) { if (event.isPropagationStopped()) { break ; } 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 ) { event.currentTarget = inst.stateNode; listener.call(undefined , event) event.currentTarget = null ; }
补充 除了SimpleEventPlugin,React中还有其他Plugin来处理不同的事件,比如React针对input/select/textarea实现的特殊的onChange,需要ChangeEventPlugin来处理。另外还有特殊的topLevelType,这是对于浏览器特定事件的处理,比如transitionEnd, webkitTransitionEnd, MozTransitionEnd和oTransitionEnd都会集合为topAnimationEnd。
参考
The React and React Native Event System Explained: A Harmonious Coexistence
React源码