react独特的事件机制(react-events)


react事件机制独特在哪里?

function ActionLink() {
    
    
  function handleClick(e) {
    
    
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={
    
    handleClick}>
      Click me
    </a>
  );
}

当我们在组件上设置事件处理器时,React内部自定义了一套事件系统,在这个系统上统一进行事件订阅和分发。
具体来讲,React利用事件委托机制在Document上统一监听DOM事件,再根据触发的target将事件分发到具体的组件实例。另外上面e是一个合成事件对(SyntheticEvent), 而不是原始的DOM事件对象。


为什么要这么特别,自定义一套事件系统:

React 事件概述:

React 根据W3C 规范来定义自己的事件系统,其事件被称之为合成事件(SyntheticEvent)。而其自定义事件系统的动机主要包含以下几个方面

  1. 抹平不同浏览器之间的兼容性差异。最主要的动机。
  2. 事件"合成",即事件自定义。事件合成既可以处理兼容性问题,也可以用来自定义事件(例如 React 的 onChange 事件)。
  3. 提供一个抽象跨平台事件机制。类似 VirtualDOM抽象了跨平台的渲染方式,合成事件(SyntheticEvent)提供一个抽象的跨平台事件机制。
  4. 可以做更多优化。例如利用事件委托机制,几乎所有事件的触发都代理到了 document,而不是 DOM 节点本身,简化了 DOM,事件处理逻辑,减少了内存开销,达到性能优化。(React 自身模拟了一套事件冒泡的机制)
  5. 可以干预事件的分发。V16引入 Fiber 架构,React 可以通过干预事件的分发以优化用户的交互体验。

注:「几乎」所有事件都代理到了 document,说明有例外,比如audio、video标签的一些媒体事件(如 onplay、onpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数(dispatchEvent)进行绑定。



什么是合成事件:

合成事件:

  1. 与浏览器事件处理稍微有不同的是,React中的事件处理程序所接收的事件参数是被称为“合成事件(SyntheticEvent)”的实例。
    合成事件是对浏览器原生事件跨浏览器的封装,并与浏览器原生事件有着同样的接口,如stopPropagation(),preventDefault()等,并且这些接口是跨浏览器兼容的。
  2. 如果需要使用浏览器原生事件可以通过合成事件的nativeEvent属性获取

执行流程:

事件注册:

React 组件挂载阶段根据组件内的声明的事件类型(onclick、onchange 等),在 document 上注册事件(使用addEventListener),并指定统一的回调函数 dispatchEvent。换句话说,document 上不管注册的是什么事件,都具有统一的回调函数 dispatchEvent。也正是因为这一事件委托机制,对于同一种事件类型,不论在 document 上注册了几次,最终也只会保留一个有效实例,这能减少内存开销。在这里插入图片描述
例子:

class Com2 extends Component {
    
    

    hancleClick1 = () => {
    
    }
    hancleClick2 = () => {
    
    }
    
    render() {
    
    
        return (
            <div>
                <button onClick={
    
    this.hancleClick1}>按钮1</button>
                <button onClick={
    
    this.hancleClick2}>按钮2</button>
            </div>
        );
    }
}

上述代码中,事件类型都是onClick,由于 React 的事件委托机制,会指定统一的回调函数 dispatchEvent,所以最终只会在 document 上保留一个 click 事件,类似document.addEventListener('click', dispatchEvent),从这里也可以看出 React 的事件是在 DOM 事件流的冒泡阶段被触发执行。

事件存储:

React 为了在触发事件时可以查找到对应的回调去执行,会把组件内的所有事件统一地存放到一个对象中(listenerBank)。而存储方式如图,首先会根据事件类型分类存储,例如 click 事件相关的统一存储在一个对象中,回调函数的存储采用键值对(key/value)的方式存储在对象中,key 是组件的唯一标识 id,value 对应的就是事件的回调函数。
在这里插入图片描述
目前为止,流程如下:

在这里插入图片描述

事件触发/执行:

React 的事件触发只会发生在 DOM 事件流的冒泡阶段,因为在 document 上注册时就默认是在冒泡阶段被触发执行。当事件冒泡到 document 时,document上绑定事件ReactEventListener.dispatchEvent会对事件进行分发,根据之前存储的事件类型(type)组件标识(key)找到触发事件的组件。获取到触发这个事件的元素,遍历这个元素的所有父元素,依次对每一级元素进行处理。构造合成事件,将每一级的合成事件存储在 eventQueue事件队列中,然后批量执行存储的回调函数,回调函数的执行分为两步:
第一步是将所有的合成事件放到事件队列里面,
第二步逐个执行(React合成事件的冒泡并不是真的冒泡,而是eventQueue的遍历模拟出来的)

注意: 遍历的过程中,通过 isPropagationStopped判断当前事件是否执行了阻止冒泡方法。如果阻止了冒泡,停止遍历,否则通过 executeDispatch执行合成事件。
最后释放处理完成的事件。

在这里插入图片描述

总结:

React合成事件的优点:

  1. 几乎所有的事件代理(delegate)到 document ,达到性能优化的目的
  2. 对于每种类型的事件,拥有统一的分发函数 dispatchEvent
  3. 事件对象(event)是合成对象(SyntheticEvent),不是原生的,其具有跨浏览器兼容的特性
  4. react内部事件系统实现可以分为两个阶段: 事件注册、事件分发,几乎所有的事件均委托到document上,而document上事件的回调函数只有一个:ReactEventListener.dispatchEvent,然后进行相关的分发
  5. 对于冒泡事件,是在 document 对象的冒泡阶段触发。对于非冒泡事件,例如focus,blur,是在 document 对象的捕获阶段触发,最后在 dispatchEvent 中决定真正回调函数的执行



额外的:

事件和原生事件的执行顺序:

由上面的流程我们可以理解:

  • react的所有事件都挂载在document中
  • 当真实dom触发后冒泡到document后才会对react事件进行处理
  • 所以原生的事件会先执行
  • 然后执行react合成事件
  • 最后执行真正在document上挂载的事件



react事件和原生事件可以混用吗?

react事件和原生事件最好不要混用。

原生事件中如果执行了stopPropagation方法,则会导致其他react事件失效因为所有元素的事件将无法冒泡到document上,就意味着没有分发并执行合成事件队列

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/108102356