简单来说,在挂载的时候,通过listenerBank把事件存起来了,触发的时候document进行dispatchEvent,找到触发事件的最深的一个节点,向上遍历拿到所有的callback放在eventQueue,根据事件类型构建event对象,遍历执行eventQueue,不简单点说,我们可以查看一下React对于事件处理的源码实现,commit id为4ab6305,TAG是React16.10.2,在React17不再往document上挂事件委托,而是挂到DOM容器上,目录结构都有了很大更改,我们还是依照React16,首先来看一下事件的处理流程。
/** * Summary of `ReactBrowserEventEmitter` event handling: * * - Top-level delegation is used to trap most native browser events. This * may only occur in the main thread and is the responsibility of * ReactDOMEventListener, which is injected and can therefore support * pluggable event sources. This is the only work that occurs in the main * thread. * * - We normalize and de-duplicate events to account for browser quirks. This * may be done in the worker thread. * * - Forward these native events (with the associated top-level type used to * trap it) to `EventPluginHub`, which in turn will ask plugins if they want * to extract any synthetic events. * * - The `EventPluginHub` will then process each event by annotating them with * "dispatches", a sequence of listeners and IDs that care about that event. * * - The `EventPluginHub` then dispatches the events. */ /** * React和事件系统概述: * * +------------+ . * | DOM | . * +------------+ . * | . * v . * +------------+ . * | ReactEvent | . * | Listener | . * +------------+ . +-----------+ * | . +--------+|SimpleEvent| * | . | |Plugin | * +-----|------+ . v +-----------+ * | | | . +--------------+ +------------+ * | +-----------.--->|EventPluginHub| | Event | * | | . | | +-----------+ | Propagators| * | ReactEvent | . | | |TapEvent | |------------| * | Emitter | . | |<---+|Plugin | |other plugin| * | | . | | +-----------+ | utilities | * | +-----------.--->| | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |Enter/Leave| * + . +-------+|Plugin | * +-------------+ . +-----------+ * | application | . * |-------------| . * | | . * | | . * +-------------+ . * . */在packages\react-dom\src\events\ReactBrowserEventEmitter.js中就描述了上边的流程,并且还有相应的英文注释,使用google翻译一下,这个太概述了,所以还是需要详细描述一下,在事件处理之前,我们编写的JSX需要经过babel的编译,创建虚拟DOM,并处理组件props,拿到事件类型和回调fn等,之后便是事件注册、存储、合成、分发、执行阶段。
Top-level delegation用于捕获最原始的浏览器事件,它主要由ReactEventListener负责,ReactEventListener被注入后可以支持插件化的事件源,这一过程发生在主线程。
React对事件进行规范化和重复数据删除,以解决浏览器的问题,这可以在工作线程中完成。
将这些本地事件(具有关联的顶级类型用来捕获它)转发到EventPluginHub,后者将询问插件是否要提取任何合成事件。
然后EventPluginHub将通过为每个事件添加dispatches(引用该事件的侦听器和ID的序列)来对其进行注释来进行处理。
再接着,EventPluginHub会调度分派事件。
事件注册首先会调用setInitialDOMProperties()判断是否在registrationNameModules列表中,在的话便注册事件,列表包含了可以注册的事件。
// packages\react-dom\src\client\ReactDOMComponent.js line 308 function setInitialDOMProperties( tag: string, domElement: Element, rootContainerElement: Element | Document, nextProps: Object, isCustomComponentTag: boolean, ): void { for (const propKey in nextProps) { if (!nextProps.hasOwnProperty(propKey)) { continue; } const nextProp = nextProps[propKey]; if (propKey === STYLE) { if (__DEV__) { if (nextProp) { // Freeze the next style object so that we can assume it won't be // mutated. We have already warned for this in the past. Object.freeze(nextProp); } } // Relies on `updateStylesByID` not mutating `styleUpdates`. setValueForStyles(domElement, nextProp); }else if(/* ... */){ // ... } else if (registrationNameModules.hasOwnProperty(propKey)) { // 对事件名进行合法性检验,只有合法的事件名才会被识别并进行事件绑定 if (nextProp != null) { if (__DEV__ && typeof nextProp !== 'function') { warnForInvalidEventListener(propKey, nextProp); } ensureListeningTo(rootContainerElement, propKey); // 开始注册事件 } } else if (nextProp != null) { setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag); } } }