Virtual DOM虚拟DOM极大的提高了React性能,并且由于Virtual DOM在内存中是以对象的形式存在的,添加事件也非常简单。基于SyntheticEvent合成事件层,定义的事件处理器会接收到SyntheticEvent对象的实例,不会存在兼容性问题,对于开发非常友好。
合成事件的绑定方式与原生HTML事件监听器属性很相似:
<button onClick={this.handleClick}>Test</button>
在JSX中使用驼峰命名来写事件的属性名,props的值可以是任意类型,这里是一个函数指针。
React对合成事件做了两件事(事件委派和自动绑定)
事件委派
即事件代理机制,事件并不直接绑定到真实节点上,而是使用事件监听器把事件绑定在结构的最外层,用映射机制来保存了所有组件内部的事件监听和处理函数。在组件挂载和卸载时变更监听器上的对象,事件发生时事件监听器会根据映射来调用事件处理函数,简化了事件处理和回收机制,提高了效率。
自动绑定
React组件中每个方法的上下文都指向了组件的实例,自动绑定this为当前组件。
绑定事件处理器内的this有三种方法:
1)使用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>;
}
}
2)构造器内声明
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() {
// 通过bind方法实现,可以传递参数
return <button onClick={this.handleClick}>Test</button>;
}
}
3)箭头函数
箭头函数会自动绑定定义此函数作用域的 this
import React, { Component } from 'react';
class App extends Component {
handleClick = (e, arg) => { console.log(e, arg); }
render() {
return <button onClick={this.handleClick}>Test</button>;
}
}
原生事件的使用
React生命周期函数componentDidMount会在组件装载完成并且浏览器生成真实DOM元素后执行,因此我们可以在componentDidMount方法内为元素绑定原生事件。
import React, { Component } from 'react';
class NativeEventDemo extends Component {
componentDidMount() {
this.refs.button.addEventListener('click', e => {
this.handleClick(e);
});
}
handleClick(e) {
console.log(e);
}
componentWillUnmount() {
this.refs.button.removeEventListener('click');
}
render() {
return <button ref="button">Test</button>;
}
}
我们在组件装载完成绑定监听事件之后应再组件卸载时移除监听事件,以免出现内存泄漏的问题。
在一定场景下可以使用原生事件帮助处理事件逻辑,但应尽量减少合成事件和原生事件的混用,或者使用e.target来判断事件监听器的真实元素,来避免原生事件的冒泡。
注意:React 的合成事件系统只是原生 DOM 事件系统的一个子集。
阻止 React 事件冒泡的行为只能用于 React 合成事件系统 中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,却可以阻止 React 合成 事件的传播
一个合成事件和原生事件混用的小demo 👇
import React, { Component } from 'react';
class QrCode extends Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
// this.handleClickQr = this.handleClickQr.bind(this);
this.state = {
active: false
}
}
componentDidMount(){
document.body.addEventListener("click", (e) => {
if(e.target && e.target.matches('button.qr')){
return;
}
this.setState({
active: false
});
});
document.querySelector(".code").addEventListener("click", (e) => {
e.stopPropagation();
});
}
componentWillUnmount(){
document.body.removeEventListener("click");
document.querySelector(".code").removeEventListener("click");
}
handleClick(){
let _this = this;
_this.setState({
active: !_this.state.active
});
}
render() {
return (
<div className="qr-wrapper">
<button className="qr" onClick={this.handleClick}>二维码</button><br/>
<div
className="code"
style={{ display: this.state.active ? 'inline-block' : 'none' }}>
<img src="/images/footer-logo.png" alt="qr" />
</div>
</div>
// <div className="qr-wrapper">
// <button className="qr" onClick={this.handleClick}>二维码</button>
// <div
// className="code"
// style={{ display: this.state.active ? 'block' : 'none' }}
// onClick={this.handleClickQr}>
// <img src="/images/footer-logo.png" alt="qr" />
// </div>
// </div>
);
}
}
export default QrCode;