addeventlistener事件参数_「React进阶系列」史上最全React事件机制详解

1ade1bde3a41d437dec264332895cd47.png

框架总览


  • DOM事件流的三个阶段
  • 关于React事件的疑问
  • React事件绑定机制
  • React事件和原生事件有什么区别
  • React事件和原生事件的执行顺序,可以混用吗
  • React事件如何解决跨浏览器兼容
  • React stopPropagation 与 stopImmediatePropagation
  • 从React的事件机制源码看整个流程
  • 基本流程
  • 事件注册
  • 事件触发
  • 总结
  • 站在巨人肩上

DOM事件流的三个阶段

3df9a39d7bca38f938893cc6a836e928.png

1、事件捕获阶段

当某个事件触发时,文档根节点最先接受到事件,然后根据DOM树结构向具体绑定事件的元素传递。该阶段为父元素截获事件提供了机会。

事件传递路径为:

window —> document —> boy —> div—> text

2、目标阶段

具体元素已经捕获事件。之后事件开始向根节点冒泡。

3、事件冒泡阶段

该阶段的开始即是事件的开始,根据DOM树结构由具体触发事件的元素向根节点传递。

事件传递路径:

text—> div —> body —> document —> window

使用addEventListener函数在事件流的的不同阶段监听事件。

DOMEle.addEventListener(‘事件名称’,handleFn,Boolean);

此处第三个参数Boolean即代表监听事件的阶段;

为true时,在在捕获阶段监听事件,执行逻辑处理;

为false时,在冒泡阶段监听事件,执行逻辑处理。

3796e720a3fa1fa85aea7bc47659efe7.png

关于React事件的疑问

1.React事件绑定机制

考虑到浏览器的兼容性和性能问题,React 基于 Virtual DOM 实现了一个SyntheticEvent(合成事件)层,我们所定义的事件处理器会接收到一个SyntheticEvent对象的实例。与原生事件直接在元素上注册的方式不同的是,react的合成事件不会直接绑定到目标dom节点上,用事件委托机制,以队列的方式,从触发事件的组件向父组件回溯直到document节点,因此React组件上声明的事件最终绑定到了document 上。用一个统一的监听器去监听,这个监听器上保存着目标节点与事件对象的映射,当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做的好处:

1.减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 document 上注册一次

2.统一规范,解决 ie 事件兼容问题,简化事件逻辑

3.对开发者友好

React Event的主要四个文件是 ReactBrowerEventEmitter.js(负责节点绑定的回调函数,该回调函数执行过程中构建合成事件对象,获取组件实例的绑定回调并执行,若有state变更,则重绘组件),ReactEventListener.js(负责事件注册和事件分发), ReactEventEmitter(负责事件的执行),EventPluginHub.js(负责事件的存储)和ReactEventEmitterMixin.js(负责事件的合成)。

2. React事件和原生事件有什么区别

带着问题用以下用代码来展示两者的区别:

  1. 点击button,最后的输出顺序是什么?
  2. B,G 处的type都是啥?
export default class Test extends React.Component {    componentDidMount() {        document.querySelector('#btn').addEventListener('click', (e) => {            console.log('A inner listener')            setTimeout(() => {                console.log('B inner listener timer', e.type)            })        })        document.body.addEventListener('click', (e) => {            console.log('C document listener')        })        window.addEventListener('click', (e) => {            console.log('D window listener')        })    }    outClick(e) {        setTimeout(() => {            console.log('E out timer', e.type)        })        console.log('F out e', e.type)    }    innerClick = (e) => {        console.log('G inner e',e.type)        e.stopPropagation()    }    render() {        return (            
点我
) }}
1. 最后的输出顺序为 A C G B2. B处的type为click,而G处的type为null
响应过程(对应第一问)

我们参照上题,详细说一下事件的响应过程:

由于我们写的几个监听事件addEventListener,都没有给第三个参数,默认值为false,所以在事件捕获阶段,原生的监听事件没有响应,react合成事件只实现了事件冒泡。所以在捕获阶段没有事件响应。

接着到了事件绑定的阶段,button上挂载了原生事件,于是输出"A",setTimeout中的"B"则进入EVENT LOOP。在上一段中,我们提到react的合成事件是挂载到document上,所以“G”没有输出。

之后进入冒泡阶段,到了div上,与上条同理,不会响应outClick,继续向上冒泡。

之后冒泡到了document上,先响应挂载到document的原生事件,输出"c"。之后接着由里向外响应合成事件队列,即输出"G",由于innerClick函数内设置了e.stopPropagation()。所以阻止了冒泡,父元素的事件响应函数没有执行。React合成事件执行e.stopPropagation()不会影响document层级之前的原生事件冒泡。但是会影响document之后的原生事件。所以没有执行body的事件响应函数。之后再处理EVENT LOOP上的事件,输出'B''.

事件池(对应第二问)

在react中,合成事件被调用后,合成事件对象会被重用,所有属性被置为null

event.constructor.release(event);

所以题目中outClick中通过异步方式访问e.type是取不到任何值的,如果需要保留属性,可以调用event.persist()事件,会保留引用。

总结

(1)命名规范不同

React事件的属性名是采用驼峰形式的,事件处理函数是一个函数;

原生事件通过addEventListener给事件添加事件处理函数

(2)React事件只支持事件冒泡。原生事件通过配置第三个参数,true为事件捕获,false为事件冒泡

(3)事件挂载目标不同

React事件统一挂载到document上;

原生事件挂载到具体的DOM上

(4)this指向不同

原生事件:

1.如果onevent事件属性定义的时候将this作为参数,在函数中获取到该参数是DOM对象。用该方法可以获取当前DOM。

2在方法中直接访问this, this指向当前函数所在的作用域。或者说调用函数的对象。

React事件:

React中this指向一般都期望指向当前组件,如果不绑定this,this一般等于undefined。

React事件需要手动为其绑定this具体原因可以参考文章: 为什么需要在 React 类组件中为事件处理程序绑定 this

(5)事件对象不同

原生js中事件对象是原生事件对象,它存在浏览器兼容性,需要用户自己处理各浏览器兼容问题;

ReactJS中的事件对象是React将原生事件对象(event)进行了跨浏览器包装过的合成事件(SyntheticEvent)。

为了性能考虑,执行完后,合成事件的事件属性将不能再访问

React事件和原生事件的执行顺序,可以混用吗

由上面的代码我们可以理解:

react的所有事件都挂载在document中

当真实dom触发后冒泡到document后才会对react事件进行处理

所以原生的事件会先执行

然后执行react合成事件

最后执行真正在document上挂载的事件

不要将合成事件与原生事件混用。执行React事对象件的e.stopPropagation()可以阻止React事件冒泡。但是不能阻止原生事件冒泡;反之,在原生事件中的阻止冒泡行为,却可以阻止 React 合成事件的传播。因为无法将事件冒泡到document上导致的

React事件如何解决跨浏览器兼容

react事件在给document注册事件的时候也是对兼容性做了处理。

78540ba3edaa6f1962353285e041923e.png

上面这个代码就是给document注册事件,内部其实也是做了对ie浏览器的兼容做了处理。

其实react内部还处理了很多,比如react合成事件:

React 根据 W3C 规范 定义了这个合成事件,所以你不需要担心跨浏览器的兼容性问题。

事件处理程序将传递 SyntheticEvent 的实例,这是一个跨浏览器原生事件包装器。 它具有与浏览器原生事件相同的接口,包括stopPropagation() 和 preventDefault() ,在所有浏览器中他们工作方式都相同。

每个SyntheticEvent对象都具有以下属性:

| 属性名 | 类型 | 描述 |

| ---- | ---- | ---- |

| bubbles | boolean | 事件是否可冒泡|

| cancelable| boolean | 事件是否可拥有取消的默认动作|

| currentTarget | DOMEventTarget| 事件监听器触发该事件的元素(绑定事件的元素)|

| defaultPrevented | boolean| 当前事件是否调用了 event.preventDefault()方法|

| eventPhase| number | 事件传播的所处阶段[0:Event.NONE-没有事件被处理,1:Event.CAPTURING_PHASE - 捕获阶段,2:被目标元素处理,3:冒泡阶段(Event.bubbles为true时才会发生)]|

| isTrusted| boolean| 触发是否来自于用户行为,false为脚本触发|

| nativeEvent | DOMEvent| 浏览器原生事件|

| preventDefault()| void | 阻止事件的默认行为|

| isDefaultPrevented() | boolean | 返回的事件对象上是否调用了preventDefault()方法|

| stopPropagation()| void| 阻止冒泡|

| isPropagationStopped() | boolean | 返回的事件对象上是否调用了stopPropagation()方法|

| target | DOMEventTarget| 触发事件的元素|

| timeStamp | number| 事件生成的日期和时间|

| type | string | 当前 Event 对象表示的事件的名称,是注册事件的句柄,如,click、mouseover…etc.|

React合成的SyntheticEvent采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。

另外,不管在什么浏览器环境下,浏览器会将该事件类型统一创建为合成事件,从而达到了浏览器兼容的目的。

React stopPropagation 与 stopImmediatePropagation

React 合成事件与原生事件执行顺序图:

837ea943bf72bfec6528886c6382f783.png

从图中我们可以得到一下结论:

(1)DOM 事件冒泡到document上才会触发React的合成事件,所以React 合成事件对象的e.stopPropagation,只能阻止 React 模拟的事件冒泡,并不能阻止真实的 DOM 事件冒泡

(2)DOM 事件的阻止冒泡也可以阻止合成事件原因是DOM 事件的阻止冒泡使事件不会传播到document上

(3)当合成事件和DOM 事件 都绑定在document上的时候,React的处理是合成事件应该是先放进去的所以会先触发,在这种情况下,原生事件对象的 stopImmediatePropagation能做到阻止进一步触发document DOM事件

stopImmediatePropagation :如果有多个

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值