🌈个人主页:前端青山
🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来React篇专栏内容:React-
前言
在前端开发中,事件处理是构建交互式用户界面的关键部分。React 作为一个流行的 JavaScript 库,提供了丰富的事件处理机制,使得开发者能够更高效地管理事件。本文将详细介绍 React 中事件处理的基本概念,包括 ES5 和 ES6 语法的事件绑定方法,并深入探讨 React 合成事件的特点及其内部机制。
目录
[1 ES5语法绑定事件](#1 ES5语法绑定事件)
[1.1 无参数的绑定](#1.1 无参数的绑定)
[1.1.1 方法一](#1.1.1 方法一)
[1.1.2 方法二](#1.1.2 方法二)
[1.1.2 有参数的绑定](#1.1.2 有参数的绑定)
[1.2 ES6语法绑定事件](#1.2 ES6语法绑定事件)
[1.2.1 无参数绑定](#1.2.1 无参数绑定)
[1.2.1.1 方法一](#1.2.1.1 方法一)
[1.2.1.2 方法二](#1.2.1.2 方法二)
[1.2.2 有参数绑定](#1.2.2 有参数绑定)
[1.2.2.1 方法一](#1.2.2.1 方法一)
[1.2.2.2 方法二](#1.2.2.2 方法二)
[1.3 合成事件的特点](#1.3 合成事件的特点)
[1.3.1 事件机制](#1.3.1 事件机制)
[1.3.2 对合成事件的理解](#1.3.2 对合成事件的理解)
[1.3.3 事件机制的流程](#1.3.3 事件机制的流程)
[1.3.4 合成事件、原生事件之间的冒泡执行关系](#1.3.4 合成事件、原生事件之间的冒泡执行关系)
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
-
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
-
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
1 ES5语法绑定事件
1.1 无参数的绑定
1.1.1 方法一
- 定义函数
javascript
handleClick(e) { // e - 事件对象
e.preventDefault();
// doSomething ...
}
- constructor 中绑定函数执行上下文
javascript
this.handleClick = this.handleClick.bind(this);
- jsx中调用
javascript
<button onClick={this.hanleClick} />
1.1.2 方法二
- 定义函数
javascript
handleClick(e) { // e - 事件对象
e.preventDefault();
// doSomething ...
}
- jsx 中调用
javascript
<button onClick={this.hanleClick.bind(this)} />
1.1.2 有参数的绑定
- 定义函数
javascript
handleClick(param1, param2, e) {
e.preventDefault();
// do something ...
}
注意此时无论多少个参数, e 一定放在最后
- jsx 中调用
javascript
<button onClick={this.hanleClick.bind(this, 'x', 'xx')} />
src/index.js
javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
// 引入时,后缀名可以省略,可以在webpack中配置
// import App from './01-App-parent-child'
// import App from './02-App-parent-child-value'
// import App from './03-App-parent-child-value-default'
// import App from './04-App-parent-child-value-default-type'
// import App from './05-App-props-children'
// import App from './06-App-mutiple-props-children'
// import App from './07-App-mouse-tracker'
// import App from './08-App-render-props'
// import App from './09-App-state-es6'
// import App from './10-App-state-es7'
// import App from './11-App-setState-function'
// import App from './12-App-setState-object'
// import App from './13-App-setState-callback'
// import App from './14-App-LifeCycle'
import App from './15-App-handler-es5'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App root={ root }/>)
src/15-App-handler-es5.jsx
javascript
import React, { Component } from 'react';
class App extends Component {
constructor (props) {
super(props)
// 中绑定函数执行上下文
this.clickHandler1Fn = this.clickHandler1.bind(this)
}
clickHandler1 (event) { // 构造函数中改变this指向 不推荐 --- 无法实现传递参数目标
console.log(this) // undefined ==构造函数中改变this指向==> App实例
console.log(event) // SyntheticBaseEvent合成事件 js原生 PointerEvent
}
clickHandler2 (event) { // 推荐 ---- 可以传递参数
console.log(this) // undefined ===jsx代码中改变了this指向===> App实例
console.log(event)
}
// 无论有多少个参数,记住,事件对象始终为最后一个参数
clickHandler3 (params1, params2, params3, event) {
console.log(params1)
console.log(params2)
console.log(params3)
console.log(event)
}
render() {
return (
<div>
<h1>es5绑定事件以及传递参数</h1>
<button onClick={ this.clickHandler1Fn }>点击-绑定事件方法1</button>
<button onClick={ this.clickHandler2.bind(this) }>点击-绑定事件方法2 - 推荐</button>
<button onClick={ this.clickHandler3.bind(this, 'a', 'b', 'c') }>点击-传递参数</button>
</div>
);
}
}
export default App;
1.2 ES6语法绑定事件
1.2.1 无参数绑定
1.2.1.1 方法一
- 定义函数
javascript
handleClick = (e) => {
e.preventDefault();
// do something ...
}
- jsx中调用
javascript
<button onClick={this.hanleClick} />
比起 es 5 中的无参数函数的绑定调用, es 6 不需要使用 bind 函数;
1.2.1.2 方法二
jsx中定义箭头函数
javascript
<button onClick={ () => {}} />
1.2.2 有参数绑定
1.2.2.1 方法一
- 定义函数
javascript
handleClick = (param1, e) => {
e.preventDefault();
// do something ...
}
- jsx调用
javascript
<button onClick={this.hanleClick.bind(this, 'x')} />
有参数时,在绑定时依然要使用 bind; 并且参数一定要传,不然仍然存在 this 指向错误问题;
1.2.2.2 方法二
- 定义函数
javascript
handleClick = (param1, e) => {
// do something ...
}
- jsx调用
javascript
<button onClick={() => this.handleClick('c')} />
// 如果需要对 event 对象进行处理的话,需要写成下面的格式
<button onClick={(e) => this.handleClick('c', e)} />
src/index.js
javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
// 引入时,后缀名可以省略,可以在webpack中配置
// import App from './01-App-parent-child'
// import App from './02-App-parent-child-value'
// import App from './03-App-parent-child-value-default'
// import App from './04-App-parent-child-value-default-type'
// import App from './05-App-props-children'
// import App from './06-App-mutiple-props-children'
// import App from './07-App-mouse-tracker'
// import App from './08-App-render-props'
// import App from './09-App-state-es6'
// import App from './10-App-state-es7'
// import App from './11-App-setState-function'
// import App from './12-App-setState-object'
// import App from './13-App-setState-callback'
// import App from './14-App-LifeCycle'
// import App from './15-App-handler-es5'
import App from './16-App-handler-es6'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App root={ root }/>)
src/16-App-handler-es6.jsx
javascript
import React, { Component } from 'react';
class App extends Component {
clickHandler1 = (event) => { // 类中定义箭头函数
console.log(this)
console.log(event)
}
clickHandler3 = (params, event) => {
console.log(params)
console.log(this)
console.log(event)
}
clickHandler4 = (params1, params2, event) => {
console.log(params1)
console.log(params2)
console.log(this)
console.log(event)
}
render() {
return (
<div>
<h1>es6绑定事件以及传递参数</h1>
<button onClick={ this.clickHandler1 }>点击-绑定事件方法1</button>
<button onClick={ (event) => { // jsx中写箭头函数
console.log(this)
console.log(event)
} }>点击-绑定事件方法2</button>
<button onClick={ this.clickHandler3.bind(this, '参数') }>传递参数1</button>
<button onClick={ (event) => this.clickHandler4('aaa', 'bbb', event) }>传递参数2</button>
</div>
);
}
}
export default App;
1.3 合成事件的特点
1.3.1 事件机制
-
react
自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。 -
react
的所有事件并没有绑定到具体的dom
节点上而是绑定在了document
上,然后由统一的事件处理程序来派发执行。 -
通过这种处理,减少了事件注册的次数,另外
react
还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。
1.3.2 对合成事件的理解
(1)对原生事件的封装
react
会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent
(合成事件对象:SyntheticEvent
是react
合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的)
(2)不同浏览器事件兼容的处理
在对事件进行合成时,
react
针对不同的浏览器,也进行了事件的兼容处理
1.3.3 事件机制的流程
1、事件注册
在组件挂载阶段,根据组件内声明的事件类型-
onclick
,onchange
等,给document
上添加事件 -addEventListener
,并指定统一的事件处理程序dispatchEvent
。
2、事件存储
完成事件注册后,将
react dom
,事件类型,事件处理函数fn
放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到listenerBank
(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。
开始事件的存储,在react
里所有事件的触发都是通过dispatchEvent
方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。react
把所有的事件和事件类型以及react
组件进行关联,把这个关系保存在了一个map
里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn
3、事件执行
1、进入统一的事件分发函数(
dispatchEvent
) 2、结合原生事件找到当前节点对应的ReactDOMComponent
对象 3、开始 事件的合成
根据当前事件类型生成指定的合成对象
封装原生事件和冒泡机制
在
listenerBank
事件池中查找事件回调并合成到event
(合成事件结束)4.处理合成事件内的回调事件(事件触发完成 end)
1.3.4 合成事件、原生事件之间的冒泡执行关系
结论:
-
原生事件阻止冒泡肯定会阻止合成事件的触发。
-
合成事件的阻止冒泡不会影响原生事件。
原因:
- 浏览器事件的执行需要经过三个阶段,
捕获阶段-目标元素阶段-冒泡阶段
。
节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件
总结
本文详细介绍了 React 中事件处理的两种主要语法:ES5 和 ES6。通过对比这两种语法的事件绑定方法,我们了解到 ES6 的箭头函数在事件处理中的优势,特别是在处理 this
指向问题时更加简洁和直观。此外,文章还深入探讨了 React 合成事件的特点,包括事件机制的流程、事件注册、事件存储和事件执行等环节。通过这些内容,开发者可以更好地理解 React 事件处理的内部机制,从而在实际开发中更加灵活地运用这些知识。