世间一切幸福,皆月影中一现的昙花;唯有孤独与痛,常伴在黄泉深处。
合成事件
- React 自己实现了一套高效的事件注册,存储,分发和重用逻辑,在 DOM 事件体系基础上做了很大改进,减少了内存消耗,简化了事件逻辑,并最大化的解决了 IE 等浏览器的不兼容问题。
合成事件特点
- React 组件上声明的事件最终绑定到了 document 这个 DOM 节点上,而不是 React 组件对应的 DOM 节点。故只有 document 这个节点上面才绑定了 DOM 原生事件,其他节点没有绑定事件。这样简化了 DOM 原生事件,减少了内存开销。
- React 以队列的方式,从触发事件的组件向父组件回溯,调用它们在 JSX 中声明的 callback。也就是 React 自身实现了一套事件冒泡机制。我们没办法用 event。stopPropagation()来停止事件传播,应该使用 event.preventDefault()。
- React 有一套自己的合成事件 SyntheticEvent,不同类型的事件会构造不同的 SyntheticEvent。
- React 使用对象池来管理合成事件对象的创建和销毁,这样减少了垃圾的生成和新对象内存的分配,大大提高了性能。
合成事件的原理
- 通过冒泡事件冒泡到顶层元素,再统一分发处理。
react 事件系统
浏览器事件(如用户点击了某个 button)触发后,DOM 将 event 传给 ReactEventListener,它将事件分发到当前组件及以上的父组件。然后由 ReactEventEmitter 对每个组件进行事件的执行,先构造 React 合成事件,然后以 queue 的方式调用 JSX 中声明的 callback 进行事件回调。
涉及主要的类
ReactEventListener:负责事件注册和事件分发。React 将 DOM 事件全都注册到 document 这个节点上,这个我们在事件注册小节详细讲。事件分发主要调用
dispatchEvent 进行,从事件触发组件开始,向父元素遍历。我们在事件执行小节详细讲。ReactEventEmitter:负责每个组件上事件的执行。
EventPluginHub:负责事件的存储,合成事件以对象池的方式实现创建和销毁,大大提高了性能。
SimpleEventPlugin 等 plugin:根据不同的事件类型,构造不同的合成事件。如 focus 对应的 React 合成事件为 SyntheticFocusEvent
事件注册
- react 声明事件
1 | render() { |
- enqueuePutListener,它负责注册 JSX 中声明的事件
1 | // inst: React Component对象 |
- enqueuePutListener 主要做两件事,一方面将事件注册到 document 这个原生 DOM 上(这就是为什么只有 document 这个节点有 DOM 事件的原因),另一方面采用事务队列的方式调用 putListener 将注册的事件存储起来,以供事件触发时回调。
- 注册事件的入口是 listenTo 方法, 它解决了不同浏览器间捕获和冒泡不兼容的问题。在 listen 方法中只有 document 节点才会调用这个 addEventListener 这个原生事件注册方法,故仅仅只有 document 节点上才有 DOM 事件。这大大简化了 DOM 事件逻辑,也节约了内存。
事件存储
- 事件存储由 EventPluginHub 来负责,它的入口在 enqueuePutListener 中的 putListener 方法
1 |
|
- 事件存储在了 listenerBank 对象中,它按照事件名和 React 组件对象进行了二维划分。
事件执行分发
- 当事件触发的,document 上的 callback 会被回调。从前面事件注册部分发现,此时回调函数为 ReactEventListener.dispatchEvent,它是事件分发的入口方法。
1 | var ReactEventListener = { |
- React 自身实现了一套冒泡机制。从触发事件的对象开始,向父元素回溯,依次调用它们注册的事件 callback。
生成合成事件
1 | // EventPluginHub.js |
- 系统启动过程中注入(injection)过来 plugins,代码如下:
1 | // react-dom模块的入口文件ReactDOM.js: |
- 默认情况下,react 注入了五种事件 plugin,针对不同的事件,得到不同的合成事件,下面看一下最常见的 SimpleEventPlugin 如何生成它对应的 React 合成事件,代码如下:
1 | // 根据不同事件类型,比如click,focus构造不同的合成事件SyntheticEvent, 如SyntheticKeyboardEvent SyntheticFocusEvent |
- 调用 EventPropagators.accumulateTwoPhaseDispatches(event)从 EventPluginHub 中获取回调函数,如何获取具体的回调函数,如下:
1 | // EventPropagators.js |
批量处理回调函数
- 回调函数的执行为两步,源码如下:
1 | function runEventQueueInBatch(events) { |
- 第一步源码如下:
1 | var eventQueue = null; |
- 第二步事件执行的入口方法为 executeDispatchesAndReleaseTopLevel,代码如下:
1 | var executeDispatchesAndReleaseTopLevel = function (e) { |