一、定义事件
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。如点击事件onClick
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
import React from "react";
export default class LearnEvent extends React.Component {
/**
* 直接在类里面定义一个方法
*/
hello () {
console.log("哈喽我是点击事件")
}
/**
* class 中定义方法的方式有多种
*/
hello2 = () => {
}
render () {
return (
<div>
{/*
onClick后面跟上事件对应的方法名
注意:这里要使用this从当前class中(也就是当前组件)
获取对应的方法,所以我们类组件中绑定事件最坑的一个点就是this指向
后面我们会着重介绍
*/}
<button onClick={this.hello}>你好</button>
</div>
)
}
}
二、this指向的问题
1、存在的问题
import React from "react";
export default class LearnEvent2 extends React.Component {
title = "this指向的问题--es5的写法";
add() {
// 当前this的取决于调用的上下文环境
console.log(this);
}
render() {
/**
*
* 在render函数里面的this指向当前组件示例,也就是当前这个类
* 所以我们可以直接在render里面使用this.xx 获取这个类里面对应的
* 属性或者方法
*/
console.log(this);
return (
<div>
{/*
在return里面 也可以直接使用this获取当前组件的属性或者方法
*/}
<div>{this.title}</div>
{/*
这里我们可以直接用this获取到当前组件的方法
但是 add 方法的内部this指向就会出现问题!
add的执行上下文为Window 由于jsx经babel编译后会开启严格模式。
所以this指向变为undefined
*/}
<button onClick={this.add}>+1</button>
</div>
);
}
}
2、使用bind改变this指向
import React from "react";
export default class LearnEvent2 extends React.Component {
constructor () {
super()
/**
* 可以再constructor里面使用this改变add的指向
* 这种方式不方便传参 不推荐使用
*/
this.add = this.add.bind(this)
}
title = "this指向的问题--es5的写法";
add() {
// 当前this的取决于调用的上下文环境
console.log(this);
}
render() {
/**
*
* 在render函数里面的this指向当前组件示例,也就是当前这个类
* 所以我们可以直接在render里面使用this.xx 获取这个类里面对应的
* 属性或者方法
*/
console.log(this);
return (
<div>
{/*
在return里面 也可以直接使用this获取当前组件的属性或者方法
*/}
<div>{this.title}</div>
{/*
这里我们可以直接用this获取到当前组件的方法
但是 add 方法的内部this指向就会出现问题!
add的执行上下文为Window 由于jsx经babel编译后会开启严格模式。
所以this指向变为undefined
*/}
<button onClick={this.add}>+1</button>
{/*
我们可以通过bind函数来改变this指向
让函数内部指向当前组件实例
并可以还可以传递参数
*/}
<button onClick={this.add.bind(this)}>bind +1</button>
</div>
);
}
}
3、使用箭头函数绑定事件
每次render都会产生一个新的实例,对性能有影响,一般情况下可以忽略不计
import React from "react";
export default class LearnEvent3 extends React.Component {
title = "this指向的问题--es6的写法";
add() {
console.log(this);
}
render() {
return (
<div>
<div>{this.title}</div>
{/*
render函数的this指向为实例自身,
所以,我们可以直接在绑定的时候使用箭头函数,
此时add函数内部的this指向当前上下文
注意:使用这箭头函数这种形式要在箭头函数里面调用对应的方法
本质上来说 事件触发的是箭头函数,然后在箭头函数里面在触发
对应的事件方法
*/}
<button onClick={() => this.add()}>+1</button>
</div>
);
}
}
4、使用箭头函数定义实例方法
import React from "react";
export default class LearnEvent4 extends React.Component {
/**
* 由于add函数式使用箭头函数定义
* 所以该函数的this 指向当前的上下文
* 也就是指向当前实例
*
* 注意:这种写法 无法进行传参,一般不推荐使用
*/
add = () => {
}
render () {
return (
<div>
<div>
<button onClick={this.add}>+1</button>
{/*
如果我们再事件后直接调用方法,那么每次render后都会
执行这个方法
这是不对的😈
*/}
{/* <button onClick={this.add()}>+1</button> */}
</div>
</div>
)
}
}
三、合成事件
1、概念
React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。
// 这个onClick就是合成事件
<button onClick={xxx}>+1</button>
2、合成事件的优势
1、进行浏览器兼容,实现更好的跨平台
React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React 提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。
2、避免垃圾回收
事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。
3、方便事件统一管理和事务机制
有兴趣的同学可以看一下源码的实现https://github.com/facebook/react/blob/75ab53b9e1de662121e68dabb010655943d28d11/packages/events/SyntheticEvent.js#L62
3、合成事件与原生事件区别
1、事件名称命名方式不同
原生事件命名为纯小写(onclick, onblur),而 React 事件命名采用小驼峰式(camelCase),如 onClick 等
// 原生事件绑定方式
<button οnclick="handleClick()">+1</button>
// React 合成事件绑定方式
const button = <button onClick={handleClick}>+1</button>
2、事件处理函数写法不同
原生事件中事件处理函数为字符串,在 React JSX 语法中,传入一个函数作为事件处理函数。
// 原生事件 事件处理函数写法
<button οnclick="handleClick()">+1</button>
// React 合成事件 事件处理函数写法
const button = <button onClick={handleClick}>+1</button>
3. 阻止默认行为方式不同
在原生事件中,可以通过返回 false 方式来阻止默认行为,但是在 React 中,需要显式使用 preventDefault() 方法来阻止
// 原生事件阻止默认行为方式
<a href="http://www.mobiletrain.org/"
οnclick="console.log('阻止原生事件'); return false"
>
阻止原生事件
</a>
// React 事件阻止默认行为方式
const handleClick = e => {
e.preventDefault();
console.log('阻止原生事件~');
}
const clickElement = <a href="http://www.mobiletrain.org/" onClick={handleClick}>
阻止原生事件
</a>
四、React 事件与原生事件执行顺序(一般了解)
import React from "react";
class LearnEvent5 extends React.Component {
parentRef;
childRef;
constructor(props) {
super(props);
this.parentRef = React.createRef();
this.childRef = React.createRef();
}
componentDidMount() {
console.log("React componentDidMount!");
this.parentRef.current?.addEventListener("click", () => {
console.log("原生事件:父元素 DOM 事件监听!");
});
this.childRef.current?.addEventListener("click", () => {
console.log("原生事件:子元素 DOM 事件监听!");
});
document.addEventListener("click", (e) => {
console.log("原生事件:document DOM 事件监听!");
});
}
parentClickFun = () => {
console.log("React 事件:父元素事件监听!");
};
childClickFun = () => {
console.log("React 事件:子元素事件监听!");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentClickFun}>
<div ref={this.childRef} onClick={this.childClickFun}>
分析事件执行顺序
</div>
</div>
);
}
}
export default LearnEvent5;
总结
1、React 所有事件都挂载在 document 对象上;
2、当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件;
3、所以会先执行原生事件,然后处理 React 事件;
4、最后真正执行 document 上挂载的事件