react 事件处理

概述

Web应用中,事件处理是重要的一环,事件处理将用户的操作行为转换为相应的逻辑执行或界面更新。在React中,处理事件响应的方式有多种,本文将详细介绍每一种处理方式的用法、使用场景和优缺点。

如果原生DOM有一个监听事件,我们可以如何操作?

  • 获取DOM元素,添加监听事件
  • 在HTML中直接进行onClick绑定

使用匿名函数

点击Button的事件响应函数是一个匿名函数,这应该是最常见的处理事件响应的方式了。这种方式的好处是,简单直接。哪里需要处理事件响应,就在哪里定义一个匿名函数处理。代码1中的匿名函数使用的是箭头函数,我们也可以不使用箭头函数:

虽然代码2的运行效果和代码1相同,但实际项目中很少见到代码2的这种写法。这是因为箭头函数解决了this绑定的问题,可以将函数体内的this绑定到当前对象,而不是运行时调用函数的对象。如果响应函数中需要使用this.state,那么代码2就无法正常运行了。所以项目中一般直接使用箭头函数定义的匿名函数作为事件响应。

使用匿名函数的缺点是:当事件响应逻辑比较复杂时,匿名函数的代码量会很大,会导致render函数变得臃肿,不容易直观地看出组件最终渲染出的元素结构。另外,每次render方法调用时,都会重新创建一个匿名函数对象,带来额外的性能开销,当组件的层级越低时,这种开销就越大,因为任何一个上层组件的变化都可能会触发这个组件的render方法。当然,在大多数情况下,这点性能损失是可以不必在意的。

使用组件方法

代码如下:

点击Button的事件响应函数是组件的方法:handleClick。这种方式的好处是:每次render方法的调用,不会重新创建一个新的事件响应函数,没有额外的性能损失。但是,使用这种方式要在构造函数中为作为事件响应的方法(handleClick),手动绑定this: this.handleClick = this.handleClick.bind(this),这是因为ES6 语法的缘故,ES6 Class 的方法默认不会把this绑定到当前的实例对象上,需要我们手动绑定。每次都手动绑定this是不是有点繁琐?好吧,让我们来看下一种方式。

使用属性初始化语法

使用ES7的属性初始化语法( property initializers ),代码可以这样写:

这样一来,再也不用手动绑定this了。但是你需要知道,这个特性还处于试验阶段,默认是不支持的。如果你是使用官方脚手架create-react-app 创建的应用,那么这个特性是默认支持的。你也可以自行在项目中引入babel的transform-class-properties插件获取这个特性支持。

事件响应函数的传参问题

事件响应函数默认是会被传入一个事件对象Event作为参数的。如果想传入其他参数给响应函数应该怎么办呢?

使用第一种方式的话很简单,直接使用新参数:

onClick的响应函数中,方法体内可以直接使用新的参数item。

使用第二种方式的话,可以把绑定this的操作延迟到render中,在绑定this的同时,绑定额外的参数:

使用第三种方式,解决方案和第二种基本一致:

不过这种方式就有点鸡肋了,因为虽然你不需要通过bind函数绑定this,但仍然要使用bind函数来绑定其他参数。

关于事件响应函数,还有一个地方需要注意。不管你在响应函数中有没有显式的声明事件参数Event,React都会把事件Event作为参数传递给响应函数,且参数Event的位置总是在其他自定义参数的后面。例如,在代码6和代码7中,handleClick的参数中虽然没有声明Event参数,但你依然可以通过arguments[1]获取到事件Event对象。

总结一下,三种事件处理的方式,第一种有额外的性能损失;第二种需要手动绑定this,代码量增多;第三种用到了ES7的特性,目前并非默认支持,需要Babel插件的支持,但是写法最为简洁,也不需要手动绑定this。一般推荐使用第二种和第三种方式。

这里呢,只是对事件处理做了一个简要的概述,下面我们将详细讲解有关事件操作的方法

注册事件

  • React注册事件与DOM的事件语法非常像
  • 语法on+事件名={事件处理程序} 比如onClick={this.handleClick}
  • 注意:React事件采用驼峰命名法,比如onMouseEnter, onClick

注意事项:

  1. 在 react 中时间的命名采用的是小驼峰形式(例如:onClick),而不是纯小写
  2. 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行

函数式组件中绑定事件

import React from "react";// 方式1
// function handlerClick() {
//   console.log(1);
// }// 方式2:推荐
// const handlerClick = () => {
//   console.log(1);
// }export default function funHello() {// function handlerClick() {//   console.log(1);// }const handlerClick = () => {console.log(1);};return (<div><button onClick={handlerClick}>点击</button></div>);
}

类组件中绑定事件

import React, { Component } from 'react'export default class classHello extends Component {constructor(props){super(props)this.state = {message: 'hello'}}// 方式1:利用箭头函数--推荐handlerMsg = () => {this.setState({message: 'hello react'})}// 方式2:利用bind等方式改变this指向// handlerMsg() {//   console.log(this);// }render() {const { message } = this.statereturn (<div><p>{message}</p><button onClick={this.handlerMsg}>更改</button>{/* <button onClick={this.handlerMsg.bind(this)}>更改</button> */}</div>)}
}

事件对象

  • 可以通过事件处理程序的参数获取到事件对象
function handleClick(e) {e.preventDefault()console.log('事件对象', e)
}
<a onClick={this.handleClick}>点我,不会跳转页面</a>// 或者
handleClick = (e) => {e.preventDefault()console.log('事件对象', e)
}
<a onClick={this.handleClick}>点我,不会跳转页面</a>handlerMsg(e) {console.log(e, this);}
<button onClick={(e) => this.handlerMsg(e)}>更改</button>handlerMsg(e, name, age) {console.log(e, this, name, age);}
<button onClick={(e) => this.handlerMsg(e, 'zs', 15)}>更改</button>

this指向问题

在jsx中事件处理函数执行时,会导致this丢失,我们可以用bind修改回来

事件处理程序中的this指向的是undefined

render方法中的this指向的而是当前react组件。只有事件处理程序中的this有问题

  • 在类中直接定义一个函数,并且将这个函数绑定到元素的onClick事件上,当前这个函数的this指向的是谁呢?---undefined
  • 默认情况下是undefined
  • 比如 <button onClick={this.handlerMsg}>更改</button>,这里的 this.handlerMsg 当点击之后并不是直接去调用函数,而是进行一个指向操作,react代码在进行编译的时候通过bable,默认会将代码转换成严格模式,在严格模式下的this指向是undefined,因此默认情况下也就是undefined
  • 我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this
  • 此时我们就需要在传入函数时,给这个函数直接绑定this
  • 我们可以利用 bind 将其修改回来,比如:<button onClick={this.changeText.bind(this)}>改变文本</button>
  • 当然也可以利用箭头函数来解决

import React, { Component } from 'react'export default class classHello extends Component {constructor(props){super(props)this.state = {message: 'hello'}}// 方式1:利用箭头函数--推荐handlerMsg = () => {this.setState({message: 'hello react'})}// 方式2:利用bind等方式改变this指向// handlerMsg() {//	this.setState({//   	message: 'hello react'//  })// }// 方式3:利用声明式//function handlerMsg() {//  this.setState({//    message: 'hello react'//  })// }render() {return (<div><p>{this.state.message}</p><button onClick={this.handlerMsg}>更改</button>{/* <button onClick={this.handlerMsg.bind(this)}>更改</button> */}</div>)}
}

this指向问题解决方案

方案1:箭头函数
方案2:bind修改this指向
方案3:类实例方法

使用箭头函数
import React, { Component } from 'react'export default class ClickClass extends Component {constructor() {super();this.msg = '消息';}// 箭头函数click = () => {console.log('类组件点击事件');console.log(this.msg);}render() {return (<div><div onClick={this.click}>类组件点击事件</div></div>)}
}

箭头函数的特点:自身没有this,访问的是外部的this

缺点:会把大量的js处理逻辑放到JSX中,将来不容易维护

直接传入箭头函数

推荐

更有利于传参

class App extends React.Component {state = {msg: 'hello react'}handleClick() {console.log(this.state.msg)}render() {return (<div><button onClick={() => {this.handleClick()}}>点我</button></div>)}
}

缺点:把大量的js逻辑写在了JSX结构中,不好维护

使用bind

使用 bind 指明 this 指向

class App extends React.Component {state = {msg: 'hello react'}handleClick() {console.log(this.state.msg)}render() {return (<div><button onClick={this.handleClick.bind(this)}>点我</button></div>)}
}

或者(在构造器中使用bind)

class App extends React.Component {constructor() {super()this.handleClick = this.handleClick.bind(this)this.msg = '消息';}state = {msg: 'hello react'}handleClick() {console.log(this.state.msg)console.log(this.msg)}render() {return (<div><button onClick={this.handleClick}>点我</button></div>)}
}

class实例方法
class App extends React.Component {state = {msg: 'hello react'}handleClick = () => {console.log(this.state.msg)}render() {return (<div><button onClick={this.handleClick}>点我</button></div>)}
}

注意:这个语法是试验性的语法,但是有babel的转义,所以没有任何问题

组件中如何给函数传递参数

箭头函数包裹进行参数传递

推荐

绑定事件函数时传递参数,可以直接绑定一个箭头函数,然后在箭头函数里面执行逻辑函数

import React, { Component } from 'react'export default class ClickClass extends Component {constructor() {super();this.msg = '消息';}click = (id) => {console.log('类组件点击事件');console.log(id);}render() {return (<div><div onClick={() => this.click(10)}>类组件点击事件</div></div>)}
}

获取event:

import React, { PureComponent } from 'react'export default class demoClassComponent extends PureComponent {constructor() {super()this.state = {title: 'hello world',}}changeTitle(event, title, title1) {this.setState({title: title,})console.log(title)console.log(event)console.log(title1)}render() {const { title } = this.statereturn (<div><p>{title}</p><buttononClick={(event) => this.changeTitle(event, '哈哈哈', '嘻嘻嘻')}>更改</button></div>)}
}

使用.bind传递参数

import React, { Component } from 'react'
export default class ClickClass extends Component {constructor() {super();this.msg = '消息';}click = (id) => {console.log('类组件点击事件');console.log(id);}render() {return (<div><div onClick={this.click.bind(this, 10)}>类组件点击事件</div></div>)}
}

Tab案例

import React, { Component } from 'react'
export default class ClickClass extends Component {constructor() {super();this.state = {tabs: ['tab1', 'tab2', 'tab3'],currentIndex: 0};}itemClick = (index) => {this.setState({currentIndex: index})}render() {const {tabs, currentIndex} = this.state;return (<div><ul>{tabs.map((item, index) => {return (<li className= {currentIndex === index ? 'active' : ''} key="item" onClick={() => this.itemClick(index)}>{item}</li>)})}</ul></div>)}
}

传递多个参数:

import React, { PureComponent } from 'react'export default class demoClassComponent extends PureComponent {constructor() {super()this.state = {title: '哈哈哈',}}click = (event, id, id1) => {console.log(event)console.log(id)console.log(id1)}render() {const { title } = this.statereturn (<div><div onClick={this.click.bind(this, 10, 20)}>类组件点击事件</div></div>)}
}

事件机制

React 基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等

在 React 中这套事件机制被称之为合成事件

合成事件(SyntheticEvent)

合成事件是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器

根据 M3C 规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口,例如:

const button = <button onClick={handleClick}></button>

如果想要获得原生 DOM 事件,可以通过 e.nativeEvent 属性获取

const handleClick = (e) => console.log(e.nativeEvent)
const button = <button onClick={handleClick}>按钮</button>

从上面可以看到 React 事件和原生事件也非常的相似,但也有一定的区别:

事件名称命名方式不同

事件处理函数书写不同

虽然 onclick 看似绑定到 DOM 元素上,但实际并不会把事件代理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件去监听

这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时, 只是在这个统一的事件监听器上插入或删除一些对象

当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升

执行顺序

关于 React 合成事件与原生事件执行顺序,可以看看下面一个例子:

结论:

  • React 所有事件都挂载在 document 对象上
  • 当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件
  • 所以会先执行原生事件,然后处理 React 事件
  • 最后真正执行 document 上挂载的事件

对应过程如图所示:

所以想要阻止不同时间段的冒泡行为,对应使用不同的方法,对应如下:

  • 阻止合成事件间的冒泡,用e.stopPropagation()
  • 阻止合成事件与最外层 document 上的事件间的冒泡,用e.nativeEvent.stoplmmediatePropagation()
  • 阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免

React 事件机制总结如下

事件绑定的方式

在 react应用中,事件名都是用小驼峰格式进行书写,例如 onclick 要改写成 onCLick

如何绑定

为了解决上面正确输出 this 的问题,常见的绑定方式有如下:

render方法中使用bind

如果使用一个类组件,在其中给某个组件/元素一个:onClick 属性,它现在并会自定绑定其 this 到当前组件,解决这个问题的方法是在事件函数后使用 .bind(this)将 this 绑定到当前组件中

这种方式在组件每次 render 渲染的时候,都会重新进行 bind 的操作,影响性能

render方法中使用箭头函数

通过 ES6 的上下文来将 this 的指向绑定给当前组件,同样再每一次 render 的时候都会生成新的方法,影响性能

constructor中bind

在 constructor 中预先 bind 当前组件,可以避免在 render 操作中重复绑定

定义阶段使用箭头函数绑定

跟上述方式三一样,能够避免在 render 操作中重复绑定,实现也非常的简单,如下:

上述四种方法的方式,区别主要如下:

  • 编写方面:方式一、方式二写法简单,方式三的编写过于冗杂
  • 性能方面:方式一和方式二在每次组件render的时候都会生成新的方法实例,性能问题欠缺。若该函数作为属性值传给子组件的时候,都会导致额外的渲染。而方式三、方式四只会生成一个方法实例
  • 综合上述,方式四是最优的事件绑定方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/54087.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QGis二次开发 —— 3、程序加载栅格tif与矢量shp文件可进行切换控制,可进行导出/导入工程(附源码)

效果 功能说明 软件可同时加载.tif栅格图片与.shp矢量图片、加载图片后可进行自由切换查看图层、可对加载的图片进行关闭 关闭后清空图层、可对加载的图片进行导出.qgs的QGIS工程、可对.qgs的QGis工程导入并导入后可进行自由切换查看图层。 源码 注意: 在加载tif栅格文件后会在…

el-table 如何实现行列转置?

在某些需求里需要用到 行列转置 的表格&#xff0c;但 el-table 提供的基本表格是不支持行列转置的&#xff0c;这样就需要对这个表格进行二次开发。下面来看具体实现的效果&#xff1a; 具体实现方式 基本原理就是对原有的可渲染的数据结构进行处理&#xff0c;表头与表格数…

计算机的错误计算(九十三)

摘要 探讨 log(y,x) 即以 x 为底 y 的对数的计算精度问题。 Log(y,x)运算是指 x 为底 y 的对数。 例1. 计算 log(123667.888, 0.999999999999999) . 不妨在Python中计算&#xff0c;则有&#xff1a; 若在 Excel 单元格中计算&#xff0c;则有几乎同样的输出&#xff1a; 然…

模型部署基础

神经网络的模型部署是将训练好的神经网络模型应用到实际系统中&#xff0c;以实现预测、分类、推荐等任务的过程。下图展示了模型从训练到部署的整个流程&#xff1a; 1.模型部署的平台 在线服务器端部署 在线服务器端部署适用于处理大模型、需要精度优先的应用场景&#xff…

CSCC2024数据库内核赛道Profile记录

同学参加CSCC2024数据库系统赛道比赛&#xff0c;我和他一起研究了一些优化的case&#xff0c;最后成功拿到全国2/325。在这里记录一下我们讨论优化过的问题&#xff08;建议把源码下下来边读边搜代码&#xff0c;否则会晕&#xff09; 行锁占用内存过大 Q&#xff1a;TPCC测…

OpenCV运动分析和目标跟踪(1)累积操作函数accumulate()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将一个图像添加到累积图像中。 该函数将 src 或其部分元素添加到 dst 中&#xff1a; dst ( x , y ) ← dst ( x , y ) src ( x , y ) if mask…

网络基础,协议,OSI分层,TCP/IP模型

网络的产生是数据交流的必然趋势&#xff0c;计算机之间的独立的个体&#xff0c;想要进行数据交互&#xff0c;一开始是使用磁盘进行数据拷贝&#xff0c;可是这样的数据拷贝效率很低&#xff0c;于是网络交互便出现了&#xff1b; 1.网络是什么 网络&#xff0c;顾名思义是…

串口接收不到数据之电阻虚焊bug分析思路

单片机和EC移远通信模块进行通信&#xff0c;相同的代码运行在相同的硬件上&#xff0c;但是一个能联网&#xff0c;一个因为没有EC的应答连不上网。 开始分析&#xff0c;排除软件问题&#xff0c;给EC模块发为什么没应答&#xff1f; 1.发送失败 2.接收失败 排除情况2&#x…

汽车租赁系统1.0版本

汽车租赁系统1.0版本比较简陋&#xff0c;以后还会有2.0、3.0……就像《我爱发明》里面的一代机器二代机器&#xff0c;三代机器一样&#xff0c;是一个迭代更新的过程&#xff08;最近比较忙&#xff0c;可能会很久&#xff09;&#xff0c;这个1.0版本很简陋&#xff0c;也请…

Python+Pytest框架,“api_key.py文件怎么编写“?

1、在"api_keyword"文件夹下新增"api_key.py" import allure import requests import json import jsonpath from deepdiff import DeepDifffrom config import *allure.title("测试用例执行") class ApiKey:allure.step(">>>:开…

跨平台开发新视角:利用Android WebView实现Web内容的原生体验

在移动应用开发领域&#xff0c;跨平台解决方案一直是一个热门话题。开发者们不断寻求能够同时在iOS和Android平台上提供一致用户体验的方法。而Android的WebView组件&#xff0c;作为一个强大的工具&#xff0c;允许开发者在Android应用中嵌入Web内容&#xff0c;为用户提供接…

Maven从入门到精通(三)

一、Settings 配置 settings.xml 用来配置 maven 项目中的各种参数文件&#xff0c;包括本地仓库、远程仓库、私服、认证等信息。 全局 settings、用户 setting、pom 的区别&#xff1a; 全局 settings.xml 是 maven 的全局配置文件&#xff0c;一般位于 ${maven.home}/conf…

安全工具 | 使用Burp Suite的10个小tips

Burp Suite 应用程序中有用功能的集合 img Burp Suite 是一款出色的分析工具&#xff0c;用于测试 Web 应用程序和系统的安全漏洞。它有很多很棒的功能可以在渗透测试中使用。您使用它的次数越多&#xff0c;您就越发现它的便利功能。 本文内容是我在测试期间学到并经常的主要…

音视频入门基础:AAC专题(4)——ADTS格式的AAC裸流实例分析

一、ADTS格式的AAC裸流实例分析 在《音视频入门基础&#xff1a;AAC专题&#xff08;3&#xff09;——AAC的ADTS格式简介》中对AAC的ADTS格式进行了简介。下面用一个具体的例子来对ADTS格式的AAC裸流进行分析。 通过《音视频入门基础&#xff1a;AAC专题&#xff08;2&#x…

SpringBoot:Web开发(基于SpringBoot使用MyBatis-Plus+JSP开发)

目录 前期准备 构建项目&#xff08;IDEA2023.1.2&#xff0c;JDK21&#xff0c;SpringBoot3.3.3&#xff09; 添加启动器 Model准备 这里我们利用MybatisX插件生成我们所需要的实体类、数据访问层以及服务层 注意选择MyBatis-Plus3以及Lombok 然后再在service接口中定义…

【算法】-单调队列

目录 什么是单调队列 区域内最大值 区域内最小值 什么是单调队列 说到单调队列&#xff0c;其实就是一个双端队列&#xff0c; 顾名思义&#xff0c;单调队列的重点分为「单调」和「队列」。「单调」指的是元素的「规律」——递增&#xff08;或递减&#xff09;。「队列」指…

电脑提示丢失mfc140u.dll的详细解决方案,mfc140u.dll文件是什么

遇到电脑显示“缺少 mfc140u.dll 文件”的错误其实是比较常见的。这种提示通常表示某个应用程序在尝试运行时未能找到它所需的关键 DLL 文件&#xff0c;导致无法正常启动。不过&#xff0c;别担心&#xff0c;本文将一步步引导你通过几种不同的方法来解决这个问题&#xff0c;…

树模式数据表设计学习

引子&#xff1a; 场景&#xff1a;某读书网站&#xff0c;支持读者评论文章&#xff0c;并且对评论支持回复功能。设计的表如下&#xff1a; 问题点&#xff1a;你想获取一个评论下所有的评论信息&#xff1f; 将所有评论一次性取出、轮巡遍历&#xff0c;获取到所有数据。 …

多人开发小程序设置体验版的痛点

抛出痛点 在分配任务时,我们将需求分为三个分支任务,分别由前端A、B、C负责: 前端A: HCC-111-实现登录功能前端B: HCC-112-实现用户注册前端C: HCC-113-实现用户删除 相应地,我们创建三个功能分支: feature_HCC-111-实现登录功能feature_HCC-112-实现用户注册feature_HCC-1…

从C语言过渡到C++

&#x1f4d4;个人主页&#x1f4da;&#xff1a;秋邱-CSDN博客☀️专属专栏✨&#xff1a;C &#x1f3c5;往期回顾&#x1f3c6;&#xff1a;单链表实现&#xff1a;从理论到代码-CSDN博客&#x1f31f;其他专栏&#x1f31f;&#xff1a;C语言_秋邱的博客-CSDN博客 目录 ​…