博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React事件机制 - 源码概览(上)
阅读量:6377 次
发布时间:2019-06-23

本文共 16973 字,大约阅读时间需要 56 分钟。

某次被问到 React事件机制的问题,关于这一块我确实不怎么清楚,因为平时大部分工作都是用 Vue,对于 React的熟悉程度只限于会用,具体实现逻辑还真没专门学习过,但是总不能就说自己不清楚吧,好在我了解 Vue的事件机制,于是就把 Vue的事件机制说了一遍,最后再来一句“我觉得 React应该和 Vue的差不多”

后来我想了下应该没那么简单,于是网上搜了下相关文章,发现果然是被我想得太简单了,Vue通过编译模板,解析出事件指令,将事件和事件回调附加到 vnode tree上,在 patch过程中的创建阶段和更新阶段都会对这个 vnode tree进行处理,拿到每个 vnode上附加的事件信息,就可以调用原生 DOM API对相应事件进行注册或移除,流程还是比较清晰的,而React则是单独实现了一套事件机制

本文以 React v16.5.2 为基础进行源码分析

基本流程

react源码的 react-dom/src/events/ReactBrowserEventEmitter.js文件的开头,有这么一大段注释:

/** * Summary of `ReactBrowserEventEmitter` event handling: * *  - Top-level delegation is used to ...... * ...... * * +------------+    . * |    DOM     |    . * +------------+    . *       |           . *       v           . * +------------+    . * | ReactEvent |    . * |  Listener  |    . * +------------+    .                         +-----------+ *       |           .               +--------+|SimpleEvent| *       |           .               |         |Plugin     | * +-----|------+    .               v         +-----------+ * |     |      |    .    +--------------+                    +------------+ * |     +-----------.--->|EventPluginHub|                    |    Event   | * |            |    .    |              |     +-----------+  | Propagators| * | ReactEvent |    .    |              |     |TapEvent   |  |------------| * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin| * |            |    .    |              |     +-----------+  |  utilities | * |     +-----------.--->|              |                    +------------+ * |     |      |    .    +--------------+ * +-----|------+    .                ^        +-----------+ *       |           .                |        |Enter/Leave| *       +           .                +-------+|Plugin     | * +-------------+   .                         +-----------+ * | application |   . * |-------------|   . * |             |   . * |             |   . * +-------------+   . *                   . *    React Core     .  General Purpose Event Plugin System */复制代码

这段注释第一段文本内容被我省略掉了,其主要是在大概描述 React的事件机制,也就是这个文件中的代码要做的一些事情,大概意思就是说事件委托是很常用的一种浏览器事件优化策略,于是 React就接管了这件事情,并且还贴心地消除了浏览器间的差异,赋予开发者跨浏览器的开发体验,主要是使用 EventPluginHub这个东西来负责调度事件的存储,合成事件并以对象池的方式实现创建和销毁,至于下面的结构图形,则是对事件机制的一个图形化描述

根据这段注释,大概可以提炼出以下几点内容:

  • React事件使用了事件委托的机制,一般事件委托的作用都是为了减少页面的注册事件数量,减少内存开销,优化浏览器性能,React这么做也是有这么一个目的,除此之外,也是为了能够更好的管理事件,实际上,React中所有的事件最后都是被委托到了 document这个顶级DOM
  • 既然所有的事件都被委托到了 document上,那么肯定有一套管理机制,所有的事件都是以一种先进先出的队列方式进行触发与回调
  • 既然都已经接管事件了,那么不对事件做些额外的事情未免有些浪费,于是 React中就存在了自己的 合成事件(SyntheticEvent),合成事件由对应的 EventPlugin负责合成,不同类型的事件由不同的 plugin合成,例如 SimpleEvent PluginTapEvent Plugin
  • 为了进一步提升事件的性能,使用了 EventPluginHub这个东西来负责合成事件对象的创建和销毁

下文均以下述这段代码为示例进行分析:

export default class MyBox extends React.Component {  clickHandler(e) {    console.log('click callback', e)  }  render() {    return (      
文本内容
) }}复制代码

事件注册

只看相关主体流程,其他诸如 vnode的创建等前提流程就不管了,从setInitialDOMProperties这个方法开始看起,这个方法主要用于遍历 ReactNodeprops对象,给最后将要真正渲染的真实 DOM对象设置一系列的属性,例如 styleclassautoFocus,也包括innerHTMLevent的处理等,示例中 .box元素的 props对象结构如下:

这个方法中有个 case,就是专门用于处理事件的:

// react-dom/src/client/ReactDOMComponent.jselse if (registrationNameModules.hasOwnProperty(propKey)) {  if (nextProp != null) {    if (true && typeof nextProp !== 'function') {      warnForInvalidEventListener(propKey, nextProp);    }    // 处理事件类型的 props    ensureListeningTo(rootContainerElement, propKey);  }}复制代码

其中的 registrationNameModules这个变量,里面存在一大堆的属性,都是与 React的事件相关:

例子中的 onClick这个 props显然符合,所以可以执行 ensureListeningTo这个方法:

// react-dom/src/client/ReactDOMComponent.jsfunction ensureListeningTo(rootContainerElement, registrationName) {  var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;  var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;  listenTo(registrationName, doc);}复制代码

这个方法中,首先判断了 rootContainerElement是不是一个 document或者 Fragment(文档片段节点),示例中传过来的是 .box这个 div,显然不是,所以 doc这个变量就被赋值为 rootContainerElement.ownerDocument,这个东西其实就是 .box所在的 document元素,把这个document传到下面的 listenTo里了,事件委托也就是在这里做的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document,然后这个 registrationName就是事件名称 onClick

接着开始执行 listenTo方法,这个方法其实就是注册事件的入口了,方法里面有这么一句:

// react-dom/src/events/ReactBrowserEventEmitter.jsvar dependencies = registrationNameDependencies[registrationName];复制代码

registrationName就是传过来的 onClick,而变量 registrationNameDependencies是一个存储了 React事件名与浏览器原生事件名对应的一个 Map,可以通过这个 map拿到相应的浏览器原生事件名,registrationNameDependencies结构如下:

可以看到,React是给事件名做了一些跨浏览器兼容事情的,比如传入 onChange事件,会自动对应上 blur change click focus等多种浏览器原生事件

接下来,遍历这个 dependencies数组,进入到以下 case

// react-dom/src/events/ReactBrowserEventEmitter.jsswitch (dependency) {  // 省略一些代码  default:    // By default, listen on the top level to all non-media events.    // Media events don't bubble so adding the listener wouldn't do anything.    var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;    if (!isMediaEvent) {      trapBubbledEvent(dependency, mountAt);    }    break;}复制代码

除了 scroll focus blur cancel close方法走 trapCapturedEvent方法,invalid submit reset方法不处理之外,剩下的事件类型全走default,执行 trapBubbledEvent这个方法,trapCapturedEventtrapBubbledEvent二者唯一的不同之处就在于,对于最终的合成事件,前者注册捕获阶段的事件监听器,而后者则注册冒泡阶段的事件监听器

由于大部分合成事件的代理注册的都是冒泡阶段的事件监听器,也就是委托到 document上注册的是冒泡阶段的事件监听器,所以就算你显示声明了一个捕获阶段的 React事件,例如 onClickCapture,此事件的响应也会晚于原生事件的捕获事件以及冒泡事件 实际上,所有原生事件的响应(无论是冒泡事件还是捕获事件),都将早于 React合成事件(SyntheticEvent),对原生事件调用 e.stopPropagation()将阻止对应 SyntheticEvent的响应,因为对应的事件根本无法到达document 这个事件委托层就被阻止掉了

二者区别不大,trapBubbledEvent用的最多,本示例也将执行这个方法,所以就跟着这个方法看下去:

// react-dom/src/events/EventListener.js// 对于本示例来说,topLevelType就是 click,element就是 documentfunction trapBubbledEvent(topLevelType, element) {  if (!element) {    return null;  }  var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;  addEventBubbleListener(element, getRawEventName(topLevelType),  // Check if interactive and wrap in interactiveUpdates  dispatch.bind(null, topLevelType));}复制代码

addEventBubbleListener这个方法接收三个参数,在本示例中,第一个参数 element其实就是 document元素,getRawEventName(topLevelType)就是 click事件,第三个参数的 dispatch就是 dispatchInteractiveEventdispatchInteractiveEvent其实最后还是会执行 dispatchEvent这个方法,只是在执行这个方法之前做了一些额外的事情,这里不需要关心,可以暂且认为二者是一样的

看下 addEventBubbleListener这个方法:

// react-dom/src/events/EventListener.jsexport function addEventBubbleListener(  element: Document | Element,  eventType: string,  listener: Function,): void {  element.addEventListener(eventType, listener, false);}复制代码

这个方法很简单,就是用 addEventListenerdocument注册了一个冒泡事件,listener这个事件的回调就是之前传入 dispatch.bind(null, topLevelType)

流程图如下:

事件分发

既然所有的事件都委托注册到了 document上,那么事件触发的时候,肯定需要一个事件分发的过程,来找到到底是哪个元素触发的事件,并执行相应的回调函数,需要注意的是,由于元素本身并没有注册任何事件,而是委托到了 document上,所以这个将被触发的事件是 React自带的合成事件,而非浏览器原生事件,但总之都是需要一个分发的过程的

在前面的 事件注册 中已经提到过,注册到 document上的事件,对应的回调函数都会触发 dispatchEvent这个方法,进入这个方法:

// react-dom/src/events/ReactDOMEventListener.jsconst nativeEventTarget = getEventTarget(nativeEvent);let targetInst = getClosestInstanceFromNode(nativeEventTarget);复制代码

首先找到事件触发的 DOMReact Component,找真实 DOM比较好找,直接取事件回调的 event参数的 target | srcElement | window即可,然后这个 nativeEventTarget对象上挂在了一个以 __reactInternalInstance开头的属性,这个属性就是 internalInstanceKey,其值就是当前 React实例对应的 React Component

然后继续往下看:

try {  // Event queue being processed in the same cycle allows  // `preventDefault`.  batchedUpdates(handleTopLevel, bookKeeping);} finally {  releaseTopLevelCallbackBookKeeping(bookKeeping);}复制代码

batchedUpdates,字面意思就是批处理更新,这里实际上就是把当前触发的事件放入了批处理队列中,其中,handleTopLevel是事件分发的核心所在:

// react-dom/src/events/ReactDOMEventListener.jslet targetInst = bookKeeping.targetInst;// Loop through the hierarchy, in case there's any nested components.// It's important that we build the array of ancestors before calling any// event handlers, because event handlers can modify the DOM, leading to// inconsistencies with ReactMount's node cache. See #1105.let ancestor = targetInst;do {  if (!ancestor) {    bookKeeping.ancestors.push(ancestor);    break;  }  const root = findRootContainerNode(ancestor);  if (!root) {    break;  }  bookKeeping.ancestors.push(ancestor);  ancestor = getClosestInstanceFromNode(root);} while (ancestor);复制代码

首先在事件回调之前,根据当前组件,向上遍历得到其所有的父组件,存储到 ancestors中,由于所有的事件都委托到了 document上,所以在事件触发后,无论是冒泡事件还是捕获事件,其在相关元素上的触发肯定是要有一个次序关系的,比如在子元素和父元素上都注册了一个鼠标点击冒泡事件,事件触发后,肯定是子元素的事件响应快于父元素,所以在事件队列里,子元素就要排在父元素前面,而在事件回调之前就要进行缓存,原因在代码的注释里也已经解释得很清楚了,大概意思就是事件回调可能会改变 DOM结构,所以要先遍历好组件层级关系,缓存起来

继续往下:

// react-dom/src/events/ReactDOMEventListener.jsfor (let i = 0; i < bookKeeping.ancestors.length; i++) {  targetInst = bookKeeping.ancestors[i];  runExtractedEventsInBatch(    bookKeeping.topLevelType,    targetInst,    bookKeeping.nativeEvent,    getEventTarget(bookKeeping.nativeEvent),  );}复制代码

使用了一个 for循环来遍历这个 React Component及其所有的父组件,执行 runExtractedEventsInBatch方法,这里的遍历方法是从前往后遍历,前面说了,我们这里分析的是 trapBubbledEvent,也就是冒泡事件,所以这里对应到组件层级上就是由子元素到父元素,如果这里是分析 trapCapturedEvent,即捕获事件,那么这个从前往后的顺序就对应父元素到子元素了 提醒一点,无论是 trapBubbledEvent还是 trapCapturedEvent,这里都是针对 document元素而不是实际的元素,不要弄混了

至于循环中调用的 runExtractedEventsInBatch方法,其实就是事件执行的入口了

事件执行

runExtractedEventsInBatch这个方法中又调用了两个方法:extractEventsrunEventsInBatchextractEvents用于构造合成事件,runEventsInBatch用于批处理 extractEvents构造出的合成事件

构造合成事件

找到合适的合成事件的 plugin

先看 extractEvents

// packages/events/EventPluginHub.jslet events = null;for (let i = 0; i < plugins.length; i++) {  // Not every plugin in the ordering may be loaded at runtime.  const possiblePlugin: PluginModule
= plugins[i]; if (possiblePlugin) { const extractedEvents = possiblePlugin.extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, ); if (extractedEvents) { events = accumulateInto(events, extractedEvents); } }}复制代码

首先遍历 plugins,这个 plugins就是所有事件合成 plugins的集合数组,一共 5种(v15.x版本是 7种),这些 plugins都位于 react-dom/src/events这个文件夹下,以单独文件的形式存在,文件名以 EventPlugin结尾的就是,它们是在 EventPluginHub初始化阶段注入进去的:

// react-dom/src/client/ReactDOMClientInjection.jsEventPluginHub.injection.injectEventPluginsByName({  SimpleEventPlugin: SimpleEventPlugin,  EnterLeaveEventPlugin: EnterLeaveEventPlugin,  ChangeEventPlugin: ChangeEventPlugin,  SelectEventPlugin: SelectEventPlugin,  BeforeInputEventPlugin: BeforeInputEventPlugin,});复制代码

extractEvents方法里用了一个 for循环,把所有的 plugin全都执行了一遍,个人理解没这个必要,找到合适的 plugin执行完之后就可以直接 break掉了 比如对于本示例的 click事件来说,合适的 pluginSimpleEventPlugin,其他的 plugin就算是进入走了一遍也只是做了个无用功而已,因为执行完其他 plugin后得到的 extractedEvents都不满足 if (extractedEvents)这个条件,无法给 events这个变量赋值或者覆盖赋值,当然,也可能这段代码还有其他比较隐秘的作用吧

possiblePlugin.extractEvents 这一句就是调用相应 plugin的构造合成事件的方法,其他的 plugin就不展开分析了,针对本示例的 SimpleEventPlugin,来看下它的 extractEvents

// react-dom/src/events/SimpleEventPlugin.jsconst dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];if (!dispatchConfig) {  return null;}复制代码

首先,看下 topLevelEventsToDispatchConfig这个对象中有没有 topLevelType这个属性,只要有,那么说明当前事件可以使用 SimpleEventPlugin构造合成事件,对于本示例来说,topLevelType就是 click,而topLevelEventsToDispatchConfig结构如下:

这些属性就是一些常见的事件名,显然 clicktopLevelEventsToDispatchConfig的一个属性名,符合条件,可以继续往下执行,下面紧跟着的是一个 switch...case的判断语句,对于本示例来说,将在下面这个 casebreak掉:

// react-dom/src/events/SimpleEventPlugin.jscase TOP_CLICK:  // 省略了一些代码  EventConstructor = SyntheticMouseEvent;  break;复制代码

SyntheticMouseEvent可以看做是 SimpleEventPlugin的一个具体的子 plugin,相当于是对 SimpleEventPlugin这个大概念的 plugin又细分了一层,除了 SyntheticMouseEvent之外还有 SyntheticWheelEventSyntheticClipboardEventSyntheticTouchEvent

从合成事件对象池中取对象

设置好具体的 EventConstructor后,继续往下执行:

// react-dom/src/events/SimpleEventPlugin.jsconst event = EventConstructor.getPooled(  dispatchConfig,  targetInst,  nativeEvent,  nativeEventTarget,);accumulateTwoPhaseDispatches(event);return event;复制代码

getPooled就是从 event对象池中取出合成事件,这种操作是 React的一大亮点,将所有的事件缓存在对象池中,可以大大降低对象创建和销毁的时间,提升性能

getPooledEventConstructor上的一个方法,这个方法是在 EventConstructor初始化的时候挂上去的,但归根到底,这个方法是位于 SyntheticEvent这个对象上,流程示意图如下:

这个 getPooled其实就是 getPooledEvent,在 SyntheticEvent初始化的过程中就被设置好初始值了:

// packages/events/SyntheticEvent.jsaddEventPoolingTo(SyntheticEvent);// 省略部分代码function addEventPoolingTo(EventConstructor) {  EventConstructor.eventPool = [];  EventConstructor.getPooled = getPooledEvent;  EventConstructor.release = releasePooledEvent;}复制代码

那么看下 getPooledEvent

// packages/events/SyntheticEvent.jsfunction getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {  const EventConstructor = this;  if (EventConstructor.eventPool.length) {    const instance = EventConstructor.eventPool.pop();    EventConstructor.call(      instance,      dispatchConfig,      targetInst,      nativeEvent,      nativeInst,    );    return instance;  }  return new EventConstructor(    dispatchConfig,    targetInst,    nativeEvent,    nativeInst,  );}复制代码

首次触发事件的时候(在本示例中就是 click事件),EventConstructor.eventPool.length0,因为这个时候是第一次事件触发,对象池中没有对应的合成事件引用,所以需要初始化,后续再触发事件的时候,就无需 new了,而是走上面那个逻辑,直接从对象池中取,通过 EventConstructor.eventPool.pop();获取合成对象实例

这里先看下初始化的流程,会执行 new EventConstructor这一句,前面说了,这个东西可以看做是 SyntheticEvent的子类,或者是由 SyntheticEvent扩展而来的东西,怎么扩展的呢,实际上是使用了一个 extend方法:

const SyntheticMouseEvent = SyntheticUIEvent.extend({  screenX: null,  screenY: null,  clientX: null,  clientY: null,  pageX: null,  pageY: null,  // 省略部分代码})复制代码

首先,SyntheticMouseEvent这个合成事件,有自己的一些属性,这些属性其实和浏览器原生的事件回调参数对象 event的属性没多大差别,都有对于当前事件的一些描述,甚至连属性名都一样,只不过相比于浏览器原生的事件回调参数对象 event来说,SyntheticMouseEvent 或者说 合成事件SyntheticEvent的属性是由 React主动生成,经过 React的内部处理,使得其上附加的描述属性完全符合 W3C的标准,因此在事件层面上具有跨浏览器兼容性,与原生的浏览器事件一样拥有同样的接口,也具备stopPropagation()preventDefault()等方法

对于本示例中的点击事件回调方法来说:

clickHandler(e) {  console.log('click callback', e)}复制代码

其中的 e其实就是 合成事件而非浏览器原生事件的 event,所以开发者无需考虑浏览器兼容性,只需要按照 w3c规范取值即可,如果需要访问原生的事件对象,可以通过 e.nativeEvent 获得

SyntheticUIEvent这个东西主要就是往 SyntheticMouseEvent上加一些额外的属性,这里不用关心,然后这个 SyntheticMouseEvent.extend又是由 SyntheticEvent扩展 (extend)来的,所以最终会 new SyntheticEvent

先看下 extend方法:

// packages/events/SyntheticEvent.jsSyntheticEvent.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;  addEventPoolingTo(Class);  return Class;};复制代码

先来了个经典的寄生组合式继承,这种寄生方法最为成熟,大多数库都是使用这种继承方法,React这里也用了它,让EventConstructor继承于 SyntheticEvent,获得 SyntheticEvent上的一些属性和方法,如前面所说的 eventPoolgetPooled

既然存在继承关系,那么 new EventConstructor这个子类,自然就会调用父类 SyntheticEventnew方法,也就是开始调用合成组件的构造器了,开始真正构造合成事件,主要就是将原生浏览器事件上的参数挂载到合成事件上,包括 clientXscreenYtimeStamp等事件属性, preventDefaultstopPropagation等事件方法,例如前面所说的通过 e.nativeEvent获得的原生事件就是在这个时候挂载上去的:

// packages/events/SyntheticEvent.jsthis.nativeEvent = nativeEvent;复制代码

挂载的属性都是经过 React处理过的,具备跨浏览器能力,同样,挂载的方法也和原生浏览器的事件方法有所不同,因为此时的事件附加在 document上的,所以调用一些事件方法,例如 e.stopPropagation()其实是针对 document元素调用的,跟原本期望的元素不是同一个,那么为了让合成事件的表现达到原生事件的效果,就需要对这些方法进行额外的处理

处理的方法也比较简单,就是加了一个标志位,例如,对于 stopPropagation来说, React对其进行了包装:

// packages/events/SyntheticEvent.jsstopPropagation: function() {  const event = this.nativeEvent;  if (!event) {    return;  }  if (event.stopPropagation) {    event.stopPropagation();  } else if (typeof event.cancelBubble !== 'unknown') {    // The ChangeEventPlugin registers a "propertychange" event for    // IE. This event does not support bubbling or cancelling, and    // any references to cancelBubble throw "Member not found".  A    // typeof check of "unknown" circumvents this issue (and is also    // IE specific).    event.cancelBubble = true;  }  this.isPropagationStopped = functionThatReturnsTrue;}复制代码

首先就是拿到浏览器原生事件,然后调用对应的 stopPropagation方法,这里需要注意一下,这里的 event是由 document这个元素上的事件触发而生成的事件回调的参数对象,而非实际元素的事件回调的参数对象,说得明白点,就是给document上触发的事件,例如点击事件,调用了一下 e.stopPropagation,阻止事件继续往 document 或者 Fragment 的父级传播

// packages/events/SyntheticEvent.js// 这个函数其实就是返回了一个 true,与此对应的,还有个函数名为 functionThatReturnsFalse的函数,用来返回 falsefunction functionThatReturnsTrue() {  return true;}复制代码

关键在于 this.isPropagationStopped = functionThatReturnsTrue;这一句,相当于是设置了一个标志位,对于冒泡事件来说,当事件触发,由子元素往父元素逐级向上遍历,会按顺序执行每层元素对应的事件回调,但如果发现当前元素对应的合成事件上的 isPropagationStoppedtrue值,则遍历的循环将中断,也就是不再继续往上遍历,当前元素的所有父元素的合成事件就不会被触发,最终的效果,就和浏览器原生事件调用 e.stopPropagation()的效果是一样的

捕获事件的原理与此相同,只不过是由父级往子级遍历的罢了

这些事件方法(包括 stopPropagationpreventDefault等)一般都是在事件回调函数内调用,而事件的回调函数则是在后面的批处理操作中执行的

var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);accumulateTwoPhaseDispatches(event);复制代码

拿到所有与当前触发事件相关的元素实例和事件回调函数

上述一大堆都是从上述代码的第一句 getPooled为入口进去的,主要是为了得到合成事件,拿到基本的合成事件以后,开始对这个合成事件进行进一步的加工,也就是 accumulateTwoPhaseDispatches这个方法要做的事情,这个方法涉及到的流程比较多,画个图清晰点:

代码和调用的方法都比较琐碎,但目标很清晰,就是保存当前元素及其父元素上挂在的所有事件回调函数,包括捕获事件(captured)和冒泡事件(bubbled),保存到事件event_dispatchListeners属性上,并且将当前元素及其父元素的react实例(在 v16.x版本中,这里的实例是一个 FiberNode)保存到event_dispatchInstances属性上

拿到了所有与事件相关的元素实例以及事件的回调函数之后,就可以对合成事件进行批量处理了

由于 React的事件机制比较复杂,要说的地方有点多,所以分为了两篇文章,剩余分析部分请参见文章

转载地址:http://pzxqa.baihongyu.com/

你可能感兴趣的文章
《VMware 网络技术:原理与实践》—— 2.3 OSI模型
查看>>
金融安全资讯精选 2017年第十五期:普华永道消费者隐私信息保护调研称69%的企业无力面对网络攻击,中小银行转型系统整合中的建议...
查看>>
读书笔记之《实战Java虚拟机》(9):Class 文件结构
查看>>
面对区块链这项全新的技术,传统投资产生了焦虑
查看>>
1024城市峰会 | 当A.I.邂逅古都西安
查看>>
好看的卡片阴影
查看>>
理解 Mach O 并提高程序启动速度
查看>>
Vue实战篇(PC端商城项目)
查看>>
每周记录(二)
查看>>
你要做的是产品经理,不是作图经理!
查看>>
编码、摘要和加密(一)——字节编码
查看>>
JavaEE 项目常见错误汇总
查看>>
快速掌握Python基础语法(下)
查看>>
java虚拟机——运行时数据区域
查看>>
【Android自定义View】绘图之文字篇(三)
查看>>
适配iOS 11和iPhoneX屏幕适配遇到的一些坑
查看>>
Fetch API 简单封装
查看>>
给媳妇做一个记录心情的小程序
查看>>
iOS App无需跳转系统设置自动连接Wi-Fi
查看>>
一道柯里化面试题
查看>>