初步理解
表象理解
先回顾一下 React 事件机制基本理解,React 自身实现了一套自己的事件机制,包括事件注册、事件合成、事件冒泡、事件派发等,虽然和原生是两码事,但是也是基于浏览器的事件机制下完成的。
我们都知道 React 的所有事件并没有绑定到具体的 DOM 节点,而是绑定到 document 上,然后由统一的事件处理程序来处理,同时也是基于浏览器的事件机制(冒泡),所有节点的时间都会在 document 上触发。
试想一下
如果一个节点同时绑定了合成和原生事件,那么禁止冒泡后执行关系是怎样?
因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理。
得出结论:
原生事件阻止冒泡肯定会组织合成事件的触发,合成事件的阻止冒泡不会影响原生事件。
原因在于,浏览器的事件执行机制是执行在前,冒泡在后,所以在原生事件中阻止冒泡会阻止合成事件的执行,反之不成立。
综上,两者最好不要一起使用,避免出现一些奇怪的问题。
意义
React 将事件全部统一交给 document 来委托处理的原因是:
- 减少内存消耗,提高性能,不需要注册那么多的事件,一种事件只需要在 document 上注册一次即可
- 统一规范,用于解决兼容性问题,简化事件逻辑
- 对开发者更加友好
对合成的理解
既然我们对 React 的事件机制有了初步的了解,那么可以知道合成事件并不是简单的合成和处理,从广义上还包括:
- 对原生事件的封装
- 对某些原生事件的升级和改造
- 不同浏览器事件的兼容处理
对原生事件的封装
上面的代码是个一个元素添加点击事件的回调函数,方法中的参数 e 其实并不是原生事件中的 event,而是 React 包装过的对象,同时原生事件中的 event 被放在了这个对象的 nativeEvent 字段。
再看下官网文档
SyntheticEvent 是 React 合成事件的基类,定义了合成事件的基础公共属性和方法。
React 会根据当前的事件类型来使用不同的合成事件对象,比如鼠标:点击事件 -- SyntheticMouseEvent,焦点事件 -- SyntheticFocusEvent 等,但都是继承与 SyntheticEvent。
对原生事件的升级和改造
对于有些 DOM 元素事件,我们进行事件绑定之后,Reacgt 并不是只处理你生命的事件类型,还会额外增加一些其他的事件,帮助我们提升交互和体验。
比如说:
当我们给 input 生命一个 onChange 事件,React 帮我们做了很多工作:
可以看到 React 不只是帮助我们注册一个 onchange 事件,还注册了很多其他的事件。
而这时候我们向文本框输入内容的时候,是可以实时得到内容,
然而原生事件只注册了一个 onchange 的话,需要在失去焦点的时候才能触发这个事件,这个缺陷 React 帮我们弥补了。
ps:图中有一个 invalid 事件是注册在当前元素而非在 document 的,可能是因为这个事件是 HTML5 表单属性特有的,需要在输入框输入的时候进行校验,如果是放到 document 上就不会生效了。
浏览器的兼容处理
react 在给 document 注册事件的时候也是做了兼容性处理的。
上面这个代码就可以看出,在给 document 注册事件的时候,内部也同时对 IE 浏览器做了兼容处理。
事件注册机制
大致流程
React 事件注册其实主要做了两件事情:
- 事件注册:组件挂载阶段,根据组件内声明的事件类型:onclick、onchange 等,给 document 添加事件监听,并制定统一的事件处理程序 dispatchEvent;
- 事件存储:就是把 React 组件内所有事件统一存放到一个对象内,缓存起来,为了在触发事件的时候能够找到对应的方法去执行。
关键步骤
首先 React 拿到将要挂载在组件的虚拟 DOM(React Element 对象)&#x