React事件处理机制详解
🌈个人主页:前端青山
🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来React篇专栏内容:React-
前言
在前端开发中,事件处理是构建交互式用户界面的关键部分。React 作为一个流行的 JavaScript 库,提供了丰富的事件处理机制,使得开发者能够更高效地管理事件。本文将详细介绍 React 中事件处理的基本概念,包括 ES5 和 ES6 语法的事件绑定方法,并深入探讨 React 合成事件的特点及其内部机制。
目录
前言
1 ES5语法绑定事件
1.1 无参数的绑定
1.1.1 方法一
1.1.2 方法二
1.1.2 有参数的绑定
1.2 ES6语法绑定事件
1.2.1 无参数绑定
1.2.1.1 方法一
1.2.1.2 方法二
1.2.2 有参数绑定
1.2.2.1 方法一
1.2.2.2 方法二
1.3 合成事件的特点
1.3.1 事件机制
1.3.2 对合成事件的理解
1.3.3 事件机制的流程
1、事件注册
2、事件存储
3、事件执行
1.3.4 合成事件、原生事件之间的冒泡执行关系
总结
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
-
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
-
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
1 ES5语法绑定事件
1.1 无参数的绑定
1.1.1 方法一
-
定义函数
handleClick(e) { // e - 事件对象
e.preventDefault();
// doSomething ...
}
-
constructor 中绑定函数执行上下文
this.handleClick = this.handleClick.bind(this);
-
jsx中调用
<button onClick={this.hanleClick} />
1.1.2 方法二
-
定义函数
handleClick(e) { // e - 事件对象
e.preventDefault();
// doSomething ...
}
-
jsx 中调用
<button onClick={this.hanleClick.bind(this)} />
1.1.2 有参数的绑定
-
定义函数
handleClick(param1, param2, e) {
e.preventDefault();
// do something ...
}
注意此时无论多少个参数, e 一定放在最后
-
jsx 中调用
<button onClick={this.hanleClick.bind(this, 'x', 'xx')} />
src/index.js
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
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 方法一
-
定义函数
handleClick = (e) => {
e.preventDefault();
// do something ...
}
-
jsx中调用
<button onClick={this.hanleClick} />
比起 es 5 中的无参数函数的绑定调用, es 6 不需要使用 bind 函数;
1.2.1.2 方法二
jsx中定义箭头函数
<button onClick={ () => {}} />
1.2.2 有参数绑定
1.2.2.1 方法一
-
定义函数
handleClick = (param1, e) => {
e.preventDefault();
// do something ...
}
-
jsx调用
<button onClick={this.hanleClick.bind(this, 'x')} />
有参数时,在绑定时依然要使用 bind; 并且参数一定要传,不然仍然存在 this 指向错误问题;
1.2.2.2 方法二
-
定义函数
handleClick = (param1, e) => {
// do something ...
}
-
jsx调用
<button onClick={() => this.handleClick('c')} />
// 如果需要对 event 对象进行处理的话,需要写成下面的格式
<button onClick={(e) => this.handleClick('c', e)} />
src/index.js
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
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 事件处理的内部机制,从而在实际开发中更加灵活地运用这些知识。