react的合成事件SyntheticEvent

1,061 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情

React合成事件

React中有自己的事件系统模式,通常称为React合成事件。跟原生事件使用上有差别。

在传统的HTML中,我们使用事件处理的写法:

<button onclick="activateLasers()">
  Activate Lasers
</button>

但是在React中稍有不同:

<button onClick={activateLasers}>
  Activate Lasers
</button>
  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

此外在React中使用事件还有几点注意:

  1. 在原生事件中我们可以直接return false来阻止默认行为。但是在React中不行,需要使用preventDefault

    function handle(e) {
        e.preventDefault();
    }
    <a onclick={handle}></a>
    
  2. 在class组件中使用事件要注意this指向

    class Demo extends React.Component {
      // 方式1:定义事件为回调函数;在元素上直接赋值
      // 该方法可以取到合成事件e,但是无法传递其他参数
      handle1 = (e) => {
        console.log(this, e)
      }
      // 方式2:定义事件为普通函数,在元素上使用bind
      // 该方法可传参,合成事件e是在最后一个
      handle2(params1, params2, e) {
        console.log(this)
        console.log(e, params1, params2)
      }
      // 方式3:定义事件为普通函数,在元素上使用回调函数
      // 该方法可以传参,可自行定义参数顺序。
      handle3(e, params) {
        console.log(this)
        console.log(e, params)
      }
      render() {
        return (<div>
          <button onClick={this.handle1}>btn1</button>
          <button onClick={this.handle2.bind(this,'参数1', '参数2')}>btn2</button>
          <button onClick={(e) => this.handle3(e, '参数3')}>btn3</button>
        </div>)
      }
    }
    

React支持的合成事件:查看

React使用合成事件机制的目的:

  1. 为了解决跨浏览器的兼容问题,抹平不同浏览器平台的细节差异。
  2. 为了统一管理事件,提高性能。主要体现在React内部实现事件委托机制上,并记录当前事件的发生状态

React是怎么实现合成事件机制的?

React合成事件原理

事件委托,也就是事件代理机制,这种机制①不会把事件处理函数绑定到真是的DOM节点上,②而是把所有事件绑定在结构最外层,使用一个统一的事件监听和处理函数。

这样做的好处在于统一管理事件。当组件挂载或者卸载时,我们只需要在这个统一管理的地方去增加或者删除一些对象。当元素事件触发时,首先去调用这个统一事件监听器,然后在映射表里找到对应的真正事件去调用真正的事件处理函数。

这样做简化了事件处理和回收机制,提高了React的效率。

另外记录事件状态,即记录事件执行的上下文,这样使得React处理事件时可以判断优先级,优先执行高优先级任务。实现了React的增量渲染的思想,可以预防掉帧使得页面更顺滑。

React 将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。

另外有,React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的

  • 所有的事件挂在document上,DOM 事件触发后冒泡到 document;React 找到对应的组件,造出一个合成事件出来;并按组件树模拟一遍事件冒泡。
  • event不是原生的,是SyntheticEvent合成事件对象
  • 和Vue事件不同,和DOM事件也不同
React17之前的事件冒泡流程:React17之后的事件冒泡:
事件冒泡跳过了react的根节点,直接委托在document上。这就使得一个页面中只能有一个版本React,如果有多个版本,事件就乱套了。事件委托不再挂在 document 上,而是挂在 DOM 容器上,也就是 ReactDom.Render 所调用的节点上
image-20220218221809174.pngimage-20220218221827184.png
React源码展示的处理原生事件列表:
// react/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap
Array [
      "abort",
      "animationEnd",
      "animationIteration",
      "animationStart",
      "auxClick",
      "beforeInput",
      "blur",
      "canPlay",
      "canPlayThrough",
      "cancel",
      "change",
      "click",
      "close",
      "compositionEnd",
      "compositionStart",
      "compositionUpdate",
      "contextMenu",
      "copy",
      "cut",
      "doubleClick",
      "drag",
      "dragEnd",
      "dragEnter",
      "dragExit",
      "dragLeave",
      "dragOver",
      "dragStart",
      "drop",
      "durationChange",
      "emptied",
      "encrypted",
      "ended",
      "error",
      "focus",
      "gotPointerCapture",
      "input",
      "invalid",
      "keyDown",
      "keyPress",
      "keyUp",
      "load",
      "loadStart",
      "loadedData",
      "loadedMetadata",
      "lostPointerCapture",
      "mouseDown",
      "mouseEnter",
      "mouseLeave",
      "mouseMove",
      "mouseOut",
      "mouseOver",
      "mouseUp",
      "paste",
      "pause",
      "play",
      "playing",
      "pointerCancel",
      "pointerDown",
      "pointerEnter",
      "pointerLeave",
      "pointerMove",
      "pointerOut",
      "pointerOver",
      "pointerUp",
      "progress",
      "rateChange",
      "reset",
      "scroll",
      "seeked",
      "seeking",
      "select",
      "stalled",
      "submit",
      "suspend",
      "timeUpdate",
      "toggle",
      "touchCancel",
      "touchEnd",
      "touchMove",
      "touchStart",
      "transitionEnd",
      "volumeChange",
      "waiting",
      "wheel",
    ]

总结:为何要合成事件

  • 兼容性和跨平台
  • 挂在统一的document上,减少内存消耗,避免频繁解绑
  • 方便事件的统一管理(事务机制)
  • dispatchEvent事件机制