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 底层,主要对合成事件做了两件事:事件委派和自动绑定。
- 事件委派
它并不会把事件处理函数直接绑定到 真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监 听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是 在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器 处理,然后在映射里找到真正的事件处理函数并调用
这样做简化了事件处理和回收机制,效率 也有很大提升。
- 自动绑定
在 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 原生事件
- 事件传播与阻止事件传播
- 浏览器原生 DOM 事件的传播可以分为 3 个阶段:事件捕获阶段、目标对象本身的事件处理 程序调用以及事件冒泡。
- React 的合成事件则并没有实现事件捕获,仅仅支持了事件冒泡机制
- 阻止原生事件传播需要使用 e.preventDefault() ,不过对于不支持该方法的浏览器(IE9 以 下),只能使用 e.cancelBubble = true 来阻止。而在 React 合成事件中,只需要使用 e.prevent- Default() 即可。
- 事件类型
React 合成事件的事件类型是 JavaScript 原生事件类型的一个子集。
- 事件绑定方式
- 直接在 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>
- 事件对象 原生 DOM 事件对象在 W3C 标准和 IE 标准下存在着差异。在低版本的 IE 浏览器中,只能使用 window.event 来获取事件对象。而在 React 合成事件系统中,不存在这种兼容性问题,在事 件处理函数中可以得到一个合成事件对象。