React源码解析(四):事件系统

笔者将编写"React源码解析"系列文章三到四篇,阐述React内部的机制。欢迎你们关注个人掘金帐号,以便能及时看到最新的文章更新推送。javascript

在前面三篇文章中,咱们阐述了react组件的构成与生命周期,setState的机制。此次咱们来谈谈React的事件处理。java

1.原生事件系统

咱们一般监听真实DOM。举🌰来讲,咱们想监听按钮的点击事件,那么咱们在按钮DOM上绑定事件和对应的回调函数便可。 遗憾的是若页面复杂且事件处理频率高,那么对网页性能是个考验。react

2.React事件系统

react的事件处理再眼花缭乱终究仍是要回归原生的事件系统,但它作的封装却很优雅。咱们直接上结论:程序员

  • React实现了SyntheticEvent层处理事件

什么意思呢?详细来讲,React并不像原生事件同样将事件和DOM一一对应,而是将全部的事件都绑定在网页的document,经过统一的事件监听器处理并分发,找到对应的回调函数并执行。按照官方文档的说法,事件处理程序将传递SyntheticEvent的实例,那么接下来咱们一探SyntheticEvent的究竟。数组

3.SyntheticEvent

1.事件注册

上文说到,既然React对事件统一进行处理,那么确定须要先注册程序员写的事件触发函数吧?那么这个过程是在哪里执行的呢?由于咱们是把事件"绑定"在"组件DOM"上,例如一个点击事件:浏览器

<Component onClick={this.handleClick}/>
复制代码

其实在这个组件挂载的时候,React就已经开始经过mountCompoent内部的_updateDOMProperties方法进行事件处理了。在这个方法中,执行的是enqueuePutListener方法去注册事件:bash

顺藤摸瓜,listenTo方法关键调用了如下两个函数:函数

  • trapBubbledEvent
  • trapCapturedEvent

熟悉原生事件系统的读者从英文翻译就能知道,两个函数是用来处理事件捕获和事件冒泡的。具体处理逻辑不分析,咱们直接看这两个函数内部:工具

上述代码中的target也就是document,也看到了熟悉的document.addEventListenerdocument.removeEventListener。正是这样统一的事件绑定减小了内存的开销。post

2.事件存储

咱们写的事件回调函数注册完毕后须要存储起来,以便触发时进行回调。存储的入口是EventPluginHub.putListener函数:

可见全部的回调函数都以二维数组的形式存储在listenerBank中,根据组件对应的key来进行管理。

3.事件分发

事件注册和事件存储咱们已经清楚了,如今咱们看下当事件触发时,React是如何进行事件分发和找到对应回调函数并执行的。分发入口在ReactDOMEventListener.jshandleTopLevelImpl:

上述代码咱们理清了流程:由于事件回调函数执行后可能致使DOM结构的变化,那么React先将当前的结构以数组的形式存储起来,依次遍历执行。 上述函数的_handleTopLevel最终对回调函数进行处理,看下源码:

代码中出现了新角色:EventPluginHub.extractEvents。查阅相关资料,得知extractEvents方法是用于合成事件的,也就是根据事件类型的不一样,合成不一样的跨浏览器的SyntheticEvent对象的实例,好比SyntheticClickEvent。而EventPluginHub顾名思义是React进行合成事件时所用的工具插件:

能够看到对于不一样的事件,React将使用不一样的功能插件,这些插件都是经过依赖注入的方式进入内部使用的。React合成事件的过程很是繁琐,但能够归纳出extractEvents函数内部主要是经过switch函数区分事件类型并调用不一样的插件进行处理从而生成SyntheticEvent实例。有兴趣的同窗能够自行了解。

4.事件处理

React处理事件的思想与处理setState的思想相似,都是采用批处理的方法。在上面handleTopLevel方法中咱们看到最后执行了runEventQueueInBatch方法:

//事件进入队列
    EventPluginHub.enqueueEvents(events);
    //...
    EventPluginHub.processEventQueue(false);
复制代码

看下processEventQueue

上述代码遍历队列中的事件,并进入executeDispatchesAndReleaseSimulated

event.constructor.release(event);
复制代码

这行代码将React的合成事件release掉,减小内存开销。事件处理的核心入口在executeDispatchesInOrder:

var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;

executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
复制代码

重要的代码就这三行,dispatchListeners是事件回调函数,dispatchInstances是对应的组件,将这些参数传入executeDispatch后:

function executeDispatch(event, simulated, listener, inst) {
    var type = event.type || 'unknown-event';
    ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}
复制代码

invokeGuardedCallback就至关简单了:

function invokeGuardedCallback(name, func, a) {
    func(a);
}
复制代码

上面的func(a)其实就是listener(event),再往上追溯,就是dispatchListeners(dispatchInstances),这也就说明为何咱们的React事件回调函数能够拿到原生的事件了。

4.总结

React事件系统为了兼容各类版本的浏览器而作了大量工做,咱们没必要钻牛角尖去研究这些是如何实现的,与原生事件不一样的点,只在于React对事件进行统一而不是分散的存储与管理,捕获事件后内部生成合成事件提升浏览器的兼容度,执行回调函数后再进行销毁释放内存,从而大大提升网页的响应性能。

回顾:
《React源码解析(一):组件的实现与挂载》
《React源码解析(二):组件的类型与生命周期》
《React源码解析(三):详解事务与队列》 联系邮箱:ssssyoki@foxmail.com

相关文章
相关标签/搜索