1、React 为什么要写出一套自己的事件系统呢?
对于不同的浏览器,对事件存在不同的兼容性,React想实现一个兼容所有全浏览器的框架,那么就需要创建一个兼容全浏览器的事件系统,以此抹平不同浏览器的差异。
v17 之前 React 事件都是绑定在 document 上,v17 之后 React 把事件绑定在应用对应的容器 container 上,将事件绑定在同一容器统一管理,防止很多事件直接绑定在原生的 DOM 元素上。造成一些不可控的情况。
由于不是绑定在真实的 DOM 上,所以 React 需要模拟一套事件流:事件捕获-> 事件源 -> 事件冒泡,也包括重写一下事件源对象 event 。
2、React冒泡和捕获的处理
冒泡阶段和捕获阶段
- 冒泡阶段:开发者正常给 React 绑定的事件比如 onClick,onChange,默认会在模拟冒泡阶段执行。
- 捕获阶段:如果想要在捕获阶段执行可以将事件后面加上 Capture 后缀,比如 onClickCapture,onChangeCapture。
阻止冒泡
React 中如果想要阻止事件向上冒泡,可以用 e.stopPropagation() 。当子元素阻止事件冒泡之后,那么父元素中的事件将不会被触发。
阻止默认事行为
React 阻止默认行为和原生的事件也有一些区别。
- 在原生事件中
e.preventDefault()和return false可以用来阻止事件默认行为。 - 但是由于在 React 中给元素的事件并不是真正的事件处理函数。所以导致 return false 方法在 React 应用中完全失去了作用。
- 在React应用中,可以用 e.preventDefault() 阻止事件默认行为,这个方法并非是原生事件的 preventDefault ,由于 React 事件源 e 也是独立组建的,所以 preventDefault 也是单独处理的。
3、React事件系统
React 事件系统可分为三个部分:
-
第一个部分是事件合成系统,初始化会注册不同的事件插件。
-
第二个就是在一次渲染过程中,对事件标签中事件的收集,向 container 注册事件。
-
第三个就是一次用户交互,事件触发,到事件执行一系列过程。
- 批量更新
- 合成事件源
- 生成事件执行队列
什么是事件合成?
举个例子,你在button上绑定了一个click事件,但是当你在控制台查看该元素的时候,该元素身上没有click事件,但是在react最外层容易container中,你就会发现有一个click事件。
你在input上绑定了一个onChange事件,不仅自身没有onChange事件,外层容器也没有onChange事件,但多了blur,change ,focus ,keydown,keyup 等事件。
如上可以作出的总结是:
- React 的事件不是绑定在元素上的,而是统一绑定在顶部容器上,在 v17 之前是绑定在 document 上的,在 v17 改成了 app 容器上。这样更利于一个 html 下存在多个应用(微前端)。
- 绑定事件并不是一次性绑定所有事件,比如发现了 onClick 事件,就会绑定 click 事件,比如发现 onChange 事件,会绑定
[blur,change ,focus ,keydown,keyup]多个事件。 - React 事件合成的概念:React 应用中,元素绑定的事件并不是原生事件,而是React 合成的事件,比如 onClick 是由 click 合成,onChange 是由 blur ,change ,focus 等多个事件合成。
React事件插件机制
React 有一种事件插件机制,比如上述 onClick 和 onChange ,会有不同的事件插件 SimpleEventPlugin ,ChangeEventPlugin 处理。
事件插件机制中有两个对象非常重要
registrationNameModules 和registrationNameDependencies
- registrationNameModules 记录了 React 事件(比如 onBlur )和与之对应的处理插件的映射,比如上述的 onClick ,就会用 SimpleEventPlugin 插件处理,onChange 就会用 ChangeEventPlugin 处理。应用于事件触发阶段,根据不同事件使用不同的插件。
- registrationNameDependencies保存了 React 事件和原生事件对应关系。这就解释了为什么上述只写了一个 onChange ,会有很多原生事件绑定在 document 上。在事件绑定阶段,如果发现有 React 事件,比如 onChange ,就会找到对应的原生事件数组,逐一绑定。
给元素绑定的事件,最后都去了哪里?或者说,react外层容易怎么识别冒泡上来的事件属于哪个元素?或者说怎么收集事件,怎么向外层容器中注册。
给元素绑定的事件,首先都会存在于元素对应的fiber节点的memoizedProps属性中,React在diff元素的时候,发现是合成事件的话,那么就会注册事件监听器,然后遍历取出取出相对应的原生事件,绑定到外层容器上。
4、React的事件和普通的HTML事件有什么不同?
区别:
- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
- 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()来阻止默认行为。
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
- 兼容所有浏览器,更好的跨平台;
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
- 方便 react 统一管理和事务机制。
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。