React事件处理机制详解

​🌈个人主页:前端青山

🔥系列专栏: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、事件注册

2、事件存储

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(合成事件对象:SyntheticEventreact合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的)

(2)不同浏览器事件兼容的处理

在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理

1.3.3 事件机制的流程

1、事件注册

在组件挂载阶段,根据组件内声明的事件类型-onclickonchange 等,给 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 事件处理的内部机制,从而在实际开发中更加灵活地运用这些知识。

相关推荐
wangjing_052231 分钟前
C语言练习.if.else语句.strstr
c语言·开发语言
Tony_long748335 分钟前
Python学习——字符串操作方法
开发语言·c#
SoraLuna1 小时前
「Mac玩转仓颉内测版26」基础篇6 - 字符类型详解
开发语言·算法·macos·cangjie
T^T尚1 小时前
uniapp H5上传图片前压缩
前端·javascript·uni-app
出逃日志1 小时前
JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
开发语言·前端·javascript
XIE3922 小时前
如何开发一个脚手架
前端·javascript·git·npm·node.js·github
GISer_Jing2 小时前
React渲染相关内容——渲染流程API、Fragment、渲染相关底层API
javascript·react.js·ecmascript
山猪打不过家猪2 小时前
React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo
前端·javascript·react.js
科技D人生2 小时前
Vue.js 学习总结(14)—— Vue3 为什么推荐使用 ref 而不是 reactive
前端·vue.js·vue ref·vue ref 响应式·vue reactive