事件系统
Virtual DOM在内存中是以对象的形式存在,如果想要在这些对象上加事件就会比较简单。React基于Virtual DOM实现了一个合成事件层,我们所定义的事件会接受到一个合成事件对象的实例。不会存在IE浏览器兼容性的问题,同样支持事件冒泡机制。
合成事件绑定方式
React事件的绑定方式在写法上与原生HTML事件监听很相似。
<button onClick={this.handleClick}></button>
这个和JavaScript的DOM0级事件很像,但是又有一些不同:HTML的事件需要全部小写的属性名,而且jsx的属性值可以是任何类型。这里是一个函数指针。
<button onclick="handle()"></button>
React并不会像DOM0级事件那样将事件处理器直接绑定到DOM上,React仅仅是借鉴了这种写法。
合成事件的实现机制
在React底层,主要对合成事件做了两件事情:事件委派和自动绑定。
1. 事件委派
React中并不是把事件处理函数绑定到当前DOM上,而是把所有的事件绑定到结构的最外层,使用统一的事件监听器。
这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。
组件挂载和卸载时,只是在统一事件监听器上插入删除一些对象。
2. 自动绑定
在React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前的组件。而且React会对这种引用缓存,以达到CPU和内存的最大优化。
注意:在使用es6 class或者纯函数的写法,这种绑定不复存在,我们需要手动实现this绑定。
- bind方法:该方法可以帮助我们绑定事件处理器内的this,并可以向事件处理器中传入参数:
class App extends Component {
handleClick(e, arg) {
console.log(e, arg)
}
render() {
return <button onClick={this.handleClick.bind(this, 'test')}></button>
}
}
如果只绑定方法,不传递参数,stage0提供一个双冒号语法:
class App extends Component {
handleClick(e) {
console.log(e)
}
render() {
return (
<button onClick{::this.handleClick}></button>
)
}
}
- 构造器内声明:在组件的构造器内完成this的绑定,这种绑定的好处在于仅仅需要进行一次绑定,而不需要每次调用事件监听器的时候去执行绑定操作。
import React, {Component} from 'react'
class App extends Component {
constructor(props) {
super(props)
}
handleClick(e) {
console.log(e)
}
render() {
return (
<button onClick={this.handleClick}></button>
)
}
}
- 箭头函数:箭头函数自动绑定了定义此函数作用域的this,因此我们不需要再对它使用bind方法。
class App extends Component {
const handleClick = (e) => {
console.log(e)
}
render() {
return (
<button onClick={this.handleClick}></button>
)
}
}
如上集中方法,都能实现在类定义的组件中绑定this上下文的效果。
在React中使用原生事件
我们可以在componentDidMount方法中完成原生事件的绑定,此时组件已经安装完成并且在浏览器中存在真实的dom。
class App extends Component {
componentDidMount() {
this.refs.button.addEventListener('click', e => {
this.handleClick()
})
}
handleClick(e) {
console.log(e)
}
componentWillUnmount() {
this.refs.button.removeEventListener('click')
}
render() {
return <button ref='button'></button>
}
}
在React中使用DOM原生事件时,一定要在组件卸载时手动移除,否则很有可能出现内存泄漏的问题。然而合成事件在内部已经很好的帮我们处理过了。
合成事件与原生事件混用
reactEvent.nativeEvent.stopPropagation()只能用于React合成事件系统中,没办法阻止原生事件的冒泡。原生事件阻止冒泡行为可以阻止React合成事件的传播。
React合成事件系统只是原生DOM事件系统的一个子集,它仅仅实现了DOM LEVEL3事件接口,并且统一了浏览器的兼容性问题。
对比合成事件与JavaScript原生事件
我们从4个方面来对比React合成事件与JavaScript原生事件。
- 事件传播与阻止事件传播
浏览器原生DOM事件的传播可以分为3个阶段:事件捕获阶段、目标对象本身事件处理程序、事件冒泡阶段。
事件捕获会优先调用结构树最外层的元素上绑定的事件监听器,然后依次向内调用,一直调用到目标元素上的事件监听器为止。可以在e.addEventListener()的第三个参数设置为true时,为元素e注册捕获事件处理程序,并且在事件传播的第一个阶段调用。事件捕获在低于IE9的浏览器中不支持。
事件冒泡和事件捕获相反,它会从目标元素向外传播,由内而外直到最外层。
React合成事件中没有实现事件捕获,仅仅支持事件冒泡机制。
阻止原生事件传播需要使用e.preventDefault(),不过对于不支持的浏览器可以使用e.cancelBubble = true来阻止。React合成事件中使用e.preventDefault()即可。
- 事件类型
React合成事件的事件类型是JavaScript原生事件类型的子集。
- 事件绑定方式
原生事件绑定的方式如下:
1. <button onclick="alert(1)">btn</button>
2. e.onclick = function() {}
3. e.addEventListener()
React事件绑定方式比较简单:
<button onClick={this.handle.bind(this)}></button>
- 事件对象
原生DOM事件对象在不同浏览器中存在着差异。在低版本的IE浏览器中,只能使用window.event来获取事件对象。React中不存在这些差异,在事件处理函数中可以得到一个合成事件对象。