React 事件概述
- React 根据 W3C 规范来定义自己的事件系统,其事件被称之为合成事件 (SyntheticEvent),而其自定义事件系统的动机主要包含以下几个方面:
抹平不同浏览器之间的兼容性差异
,最主要的动机;既可以处理兼容性问题,也可以用来自定义事件
(例如 React 的 onChange 事件);提供一个抽象跨平台事件机制
,类似 VirtualDOM 抽象了跨平台的渲染方式,合成事件 (SyntheticEvent) 提供一个抽象的跨平台事件机制;可以做更多优化
,例如利用事件委托机制,几乎所有事件的触发都代理到了 document,而不是 DOM 节点本身,简化了 DOM 事件处理逻辑,减少了内存开销;(React 自身模拟了一套事件冒泡的机制)
- 因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理;
- 节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡;
- 原生事件阻止冒泡肯定会阻止合成事件的触发;
- 合成事件的阻止冒泡不会影响原生事件;
可以干预事件的分发
,V16 引入 Fiber 架构,React 可以通过干预事件的分发以优化用户的交互体验;- 注:「几乎」所有事件都代理到了 document,说明有例外,比如 audio、video 标签的一些媒体事件 (如 onplay、onpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数 (dispatchEvent) 进行绑定;
合成事件的理解
封装原生事件
-
SyntheticEvent 是 react 合成事件的基类,定义了合成事件的基础公共属性和方法;事件的回调方法中的参数 e,其实不是原生事件对象,而是 react 包装过的对象,同时原生事件对象被放在了属性 e.nativeEvent 内;
-
react 会根据当前的事件类型来使用不同的合成事件对象,比如鼠标单机事件 SyntheticMouseEvent,焦点事件 SyntheticFocusEvent 等,但是都是继承自 SyntheticEvent;
升级和改造原生事件
-
对于有些 dom 元素事件,进行事件绑定之后,react 并不是只处理声明的事件类型,还会额外的增加一些其他的事件,帮助我们提升交互的体验;
-
当给 input 声明个 onChange 事件,可以看到 react 不只是注册了一个 onchange 事件,还注册了很多其他事件,而这个时候向文本框输入内容的时候,是可以实时的得到内容的;然而原生只注册一个 onchange 的话,需要在失去焦点的时候才能触发这个事件,所以这个原生事件的缺陷 react 也帮我们弥补了;
处理浏览器的兼容性
-
react 在给 document 注册事件的时候也是对兼容性做了处理;
-
document 注册事件,内部其实也是做了对 ie 浏览器的兼容做了处理;
事件注册机制
事件注册
-
在 React 组件挂载阶段,根据组件内的声明的事件类型 (onclick、onchange 等),使用
addEventListener('事件类型', dispatchEvent)
在 document 上注册事件 (从这里也可以看出 React 的事件是在 DOM 事件流的冒泡阶段被触发执行),并指定统一的回调函数 dispatchEvent; -
换句话说,document 上不管注册的是什么事件,都具有统一的回调函数 dispatchEvent;也正是因为这一事件委托机制,所以对于同一种事件类型,不论在 document 上注册了几次,最终也只会保留一个有效实例,这能减少内存开销;
事件存储
-
React 为了在触发事件时可以查找到对应的回调去执行,会把组件内的所有事件统一地存放到一个对象中 (listenerBank);
-
存储方式如上图,首先会根据事件类型分类存储,例如 click 事件相关的统一存储在一个对象中,回调函数的存储采用键值对 (key/value) 的方式存储在对象中,key 是组件的唯一标识 id,value 对应的就是事件的回调函数;
事件执行机制
-
React 的事件触发只会发生在 DOM 事件流的冒泡阶段,因为在 document 上注册时就默认是在冒泡阶段被触发执行;
-
其大致流程如下:
- 进入统一的事件分发函数 (dispatchEvent);
- 结合原生事件找到当前节点对应的 ReactDOMComponent 对象;
- 开始 事件的合成:
- 根据当前事件类型生成指定的合成对象;
- 封装原生事件和冒泡机制;
- 查找当前元素以及他所有父级;
- 在 listenerBank 查找事件回调并合成到 event (合成事件结束);
- 批量处理合成事件内的回调事件 (事件触发完成 end);
注意事项
-
如果给真实的 DOM 注册事件,阻止了事件冒泡,则会导致 react 的相应事件无法触发;
-
如果给真实的 DOM 注册事件,原生事件 会先于 React 事件 运行;
-
通过 React 的事件中阻止事件冒泡,无法阻止真实的 DOM 事件冒泡;
-
React 事件 可以通过
e.nativeEvent.stopImmediatePropagation()
,阻止 document 上剩余事件的执行; -
在事件处理程序中,不要异步的使用事件对象,如果一定要使用,需要先调用
e.persist()
;
React✍️ 错误边界
上一篇