React事件机制

·  阅读 194

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)进行绑定。

React事件机制包含两个部分,先事件注册,再事件分发。接着来介绍这两种具体过程:

事件注册

React 的事件注册过程主要做了两件事:document 注册、存储事件回调。

image.png

(1)document 注册 在组件挂载(componentDidMount)过程中,在document上根据不同事件类型注册原生事件(使用addEventListener如上图),因为利用事件委托这个机制,所以不管是什么类型的事件,都会拥有同样的回调函数dispatchEvent。

function TestComponent() {
  handleFatherClick=()=>{
		// ...
  }

  handleChildClick=()=>{
		// ...
  }

  return <div className="father" onClick={this.handleFatherClick}>
	<div className="child" onClick={this.handleChildClick}>child </div>
  </div>
}
复制代码

以上的代码注册了两个onclick事件,他们都属于同一类事件,在document上注册的类似这种document.addEventListener('click', dispatchEvent),同一类事件不管注册几次只会留下一个实例,这能减少内存开销。

(2)存储事件回调 如第一张图,react会将事件存储到一个对象中(listenerBank),对象中首先会以事件类型分类存储,然后以每个事件生成唯一的id为key,回调函数为value存储回调函数。

事件分发

事件分发也就是事件触发。React 的事件触发只会发生在 DOM 事件流的冒泡阶段,因为在 document 上注册时就默认是在冒泡阶段被触发执行。

大体流程为: 1.触发事件,开始 DOM 事件流,先后经过三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段 2.事件冒泡到document对象上触发统一的事件分发函数 ReactEventListener.dispatchEvent 3.根据原生事件对象(nativeEvent)找到当前节点(即事件触发节点)对应的 ReactDOMComponent 对象 4.事件合成 (1)根据当前事件类型生成对应的合成对象 (2)封装原生事件对象和冒泡机制 (3)查找当前元素以及它所有父级 (4)在 listenerBank 中查找事件回调函数并合成到 events 中 5.批量执行合成事件(events)内的回调函数 6.如果没有阻止冒泡,会将继续进行 DOM 事件流的冒泡(从 document 到 window),否则结束事件触发

image.png 注意:以上说的阻止冒泡用的是stopImmediatePropagation,stopPropagation和stopImmediatePropagation区别是:stopPropagation只能阻止react合成事件冒泡事件,stopImmediatePropagation能阻止原生和react事件

class TestComponent extends React.Component {

  componentDidMount() {
    this.parent.addEventListener('click', (e) => {
      console.log('dom parent');
    })
    this.child.addEventListener('click', (e) => {
      console.log('dom child');
    })
    document.addEventListener('click', (e) => {
      console.log('document');
    })
    document.body.addEventListener('click', (e) => {
      console.log('body');
    })
    window.addEventListener('click', (e) => {
      console.log('window');
    })
  }

  childClick = (e) => {
    console.log('react child');
  }

  parentClick = (e) => {
    console.log('react parent');
  }

  render() {
    return (
      <div class='parent' onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div class='child' onClick={this.childClick} ref={ref => this.child = ref}>
          Click me!
        </div>
      </div>)
  }
}
复制代码

点击 child div 时,其输出如下:

image.png

React 合成事件和原生 DOM 事件的主要区别: 1.原生事件是绑定在dom元素上的,合成事件是统一绑定到document对象上。 2.由于合成事件是在事件冒泡阶段触发事件,而原生事件是在目标阶段,所以在同个元素上原生事件总比react合成事件早执行。

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改