一. React 事件机制
React基于
浏览器事件机制实现了一套自己的事件机制,包括:事件注册、事件合成、事件冒泡、事件触发等。
- 其中事件注册发生在组件挂载阶段
- 事件触发后,才会有事件冒泡
- 而事件冒泡到 document 后,才会有事件合成!!!
<1> React合成事件 与 普通的HTML事件 的区别:
-
事件命名方式:
- react :驼峰命名法 - HTML原生 :小写 -
事件绑定语法:
- react :通过 JSX 方式绑定的事件,比如 `onClick={() => this.handle()}` - HTML原生 :在 `componentDidMount生命周期`里边进行`addEventListener`绑定的事件 -
阻止事件冒泡的方法:
- react :只能过`event.preventDefault()` - HTML原生 :可以通过`return false`或 `event.stopPropagation()`
注意:
-
事件的执行顺序为原生事件先执行,合成事件再执行。
-
原生事件阻止冒泡肯定会阻止合成事件的触发。
-
合成事件的阻止冒泡不会影响原生事件。
<2> 事件代理机制
React的事件并没有绑定到具体的dom节点上,而是绑定在了document上,然后由统一的事件监听器去监听事件的触发!
React在内部维护了一个映射表来记录事件与组件的事件处理函数的对应关系。当某个事件触发时,React根据映射表将事件分派给指定的
事件处理函数。当一个组件挂载与卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。这样做简化了事件处理和回收机制,效率也提升很大。
<3> 合成事件对象
合成事件本质上是一个对象,是 React 对原生事件对象的封装。
SyntheticEvent是React合成事件的基类,定义了合成事件的基础公共属性和方法。 React会根据当前的事件类型来使用不同的合成事件对象,比如单击事件SyntheticMouseEvent,焦点事件SyntheticFocusEvent等,这些事件都继承SyntheticEvent。
大致合成过程: 【合成发生在事件冒泡阶段】
-
对原生事件的封装
- 继承SyntheticEvent基类 - 把原生事件对象被放在自己的属性 e.nativeEvent内。 -
对某些原生事件的升级和改造
react并不是只处理你声明的事件类型,还会额外的增加一些其他的事件,帮助我们提升交互的体验。
当 React 注册了 onChange 事件,会发现 React 不只是注册了一个onChange事件,还注册了很多其他事件。
- 不同浏览器事件兼容的处理
- 查找当前元素及其所有父级元素,构成 path 路径(级冒泡的路径)
- 在
listenerBank中根据 path 路径查找对应需要执行的回调函数,并将它们作为属性,合成到合成事件对象中!!!
合成事件原理:
- 当用户在为onClick添加函数时,React并没有将Click绑定到DOM上 而在document处监听所有支持的事件,
- 当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent (负责所有事件合成) -* 然后使用统一的分发函数 dispatchEvent 将封装的事件内容交由真正的处理函数执行*
<4> 事件注册 【发生在组件挂载期间】
在React组件挂载阶段,根据组件内声明的事件类型(onClick、onChange等),在document上通过
addEventListener注册事件,并指定统一的回调函数dispatchEvent。
注意:
-
在document上不管注册什么事件,都具有统一的回调函数
dispatchEvent。 -
对于同一种事件类型,不论在document上注册了几次,最终也只会保留一个有效实例,这样就可以减少内存消耗。
-
即使有多个点击事件,但是最终只会在
document上只会保留一个click事件,类似document.addEventListener('click', dispatchEvent)
<5> 事件储存 ---- listenerBank
React为了在触发事件时可以查到对应的回调去执行,会把组件内同一类型的所有事件统一存放到一个对象中
(映射表listenerBank)。【先根据事件类型分类存储,例如click事件相关的统一存储到一个对象中,回调函数的存储采用键值对的形式,key代表组件的唯一标识,value对应的就是事件的回调函数。】
<6> 组件挂载阶段,关于事件处理的关键步骤
- 首先React生成要挂载的组件的虚拟DOM(通过babel对jsx进行词法分析,然后调用React.createElement()方法返回一个对象,这个对象就是虚拟DOM)
- 然后处理组件的props,判断props内是否有声明为事件的属性比如 onClick,onChange,这个时候得到事件类型 click,change 和对应的事件处理程序 fn
- 将这些事件在document上注册
- 在组件挂载完成后,将事件处理函数存储到
listenerBank(映射表)中
<7> 事件触发时的处理流程
注意: React的事件触发只会发生在DOM事件流的冒泡阶段,因为在document上注册时就默认是在冒泡阶段被触发执行。
大致流程如下:
-
触发事件,开始DOM事件流:事件捕获阶段、处于目标阶段、事件冒泡阶段
-
当事件冒泡到document时,触发统一的事件回调函数
ReactEventListener.dispatchEvent -
根据原生事件对象(nativeEvent)找到事件触发节点对应的组件
-
开始事件的合成
- 根据当前事件类型生成指定的合成对象 - 封装原生事件和冒泡机制 - 查找当前元素以及它所有父级 - 在listenerBank查找事件回调并合成到event -
批量执行合成事件内的回调函数
-
如果没有阻止冒泡,会将继续进行 DOM 事件流的冒泡(从 document 到 window),否则结束事件触发