react系列知识---07事件系统

230 阅读4分钟

React 基于 Virtual DOM 实现了一个 SyntheticEvent (合成事件)层,我们所定义的事件 处理器会接收到一个 SyntheticEvent 对象的实例,它完全符合 W3C 标准,不会存在任何 IE 标 准的兼容性问题。

合成事件的绑定方式

下面的 JSX 代码表示为按钮添加点击事件:

<button onClick={this.handleClick}>Test</button>

如果使用 DOM0 级事件的写法,会是这样的:

<button onclick="handleClick()">Test</button>

注意:React 并不会像 DOM0 级事件那样将事件处理器直接绑定到 HTML 元素之上。React 仅仅是 借鉴了这种写法而已

合成事件的实现机制

在 React 底层,主要对合成事件做了两件事:事件委派和自动绑定。

  1. 事件委派

它并不会把事件处理函数直接绑定到 真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监 听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是 在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器 处理,然后在映射里找到真正的事件处理函数并调用

这样做简化了事件处理和回收机制,效率 也有很大提升。

  1. 自动绑定

在 React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件,但在使用 ES6 classes 或者纯 函数时,这种自动绑定就不复存在了,我们需要手动实现 this 的绑定

几种绑定的方法:

  • bind 方法
import React, { Component } from 'react';
class App extends Component {
    handleClick(e, arg) {
        console.log(e, arg);
    }
    render() {
        // 通过bind方法实现,可以传递参数
        return <button onClick={this.handleClick.bind(this, 'test')}>Test</button>;
    }
}
  • 构造器内声明
import React, { Component } from 'react';
class App extends Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(e) {
        console.log(e);
    }
    render() {
        return <button onClick={this.handleClick}>Test</button>;
    }
}
  • 箭头函数
import React, { Component } from 'react';
class App extends Component {
    const handleClick = (e) => {
        console.log(e);
    };
    render() {
        return <button onClick={this.handleClick}>Test</button>;
    }
}

import React, { Component } from 'react';
class App extends Component {
    handleClick(e) {
        console.log(e);
    }
    render() {
        return <button onClick={() => this.handleClick()}>Test</button>
    }
}

在 React 中使用原生事件

React 提供了完备的生命周期方法,其中 componentDidMount 会在组件已经完成安装并且在浏览器 中存在真实的 DOM 后调用,此时我们就可以完成原生事件的绑定

注意:在 React 中使用 DOM 原生事件时,一定要在组件卸载时手动移除,否则很 可能出现内存泄漏的问题。而使用合成事件系统时则不需要,因为 React 内部已经帮你妥善地处 理了

合成事件与原生事件混用

  • 尽量避免在 React 中混用合成事件和原生 DOM 事件。另外,用 reactEvent.nativeEvent. stopPropagation() 来阻止冒泡是不行的。阻止 React 事件冒泡的行为只能用于 React 合成事件系统 中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,却可以阻止 React 合成 事件的传播

  • React 的合成事件系统只是原生 DOM 事件系统的一个子集。它仅仅实现了 DOM Level 3 的事件接口,并且统一了浏览器间的兼容问题。有些事件 React 并没有实现,或者受某些 限制没办法去实现,比如 window 的 resize 事件。

  • 对于无法使用 React 合成事件的场景,我们还需要使用原生事件来完成

对比 React 合成事件与 JavaScript 原生事件

  1. 事件传播与阻止事件传播
  • 浏览器原生 DOM 事件的传播可以分为 3 个阶段:事件捕获阶段、目标对象本身的事件处理 程序调用以及事件冒泡。
  • React 的合成事件则并没有实现事件捕获,仅仅支持了事件冒泡机制
  • 阻止原生事件传播需要使用 e.preventDefault() ,不过对于不支持该方法的浏览器(IE9 以 下),只能使用 e.cancelBubble = true 来阻止。而在 React 合成事件中,只需要使用 e.prevent- Default() 即可。
  1. 事件类型

React 合成事件的事件类型是 JavaScript 原生事件类型的一个子集。

  1. 事件绑定方式
  • 直接在 DOM 元素中绑定:
<button onclick="alert(1);">Test</button>
  • 在 JavaScript 中,通过为元素的事件属性赋值的方式实现绑定:
el.onclick = e => { console.log(e); }
  • 通过事件监听函数来实现绑定:
el.addEventListener('click', () => {}, false);
el.attachEvent('onclick', () => {});
  • 相比而言,React 合成事件的绑定方式则简单得多:
<button onClick={this.handleClick}>Test</button>
  1. 事件对象 原生 DOM 事件对象在 W3C 标准和 IE 标准下存在着差异。在低版本的 IE 浏览器中,只能使用 window.event 来获取事件对象。而在 React 合成事件系统中,不存在这种兼容性问题,在事 件处理函数中可以得到一个合成事件对象。