ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
&emsp;&emsp;React在原生事件的基础上,重新设计了一套跨浏览器的合成事件(SyntheticEvent),在事件传播、注册方式、事件对象等多个方面都做了特别的处理。 ## 一、注册事件 &emsp;&emsp;合成事件采用声明式的注册方式,类似于设置HTML属性,但有两点不同: &emsp;&emsp;(1)事件要采用小驼峰的命名法,而不是全部小写,例如onclick要写成onClick。 &emsp;&emsp;(2)在JSX中的事件处理程序是一个函数引用,而不是一段字符串,如下代码所示,其中handle()是一个函数。 ~~~html <button onclick="handle()">提交</button> <button onClick={handle}>提交</button> ~~~ &emsp;&emsp;两个元素都注册了点击事件,其中前者采用了传统的事件注册,后者使用了合成事件,通过对比可发现它们之间有两处明显的不同。 &emsp;&emsp;React的事件类型是原生事件类型的一个子集,在上面的示例中,为按钮添加了Mouse类型的onClick事件。除了常规的Focus、Form、Touch等类型之外,React还支持Selection、Wheel、Media等有特殊功能的事件类型。 ## 二、事件传播 &emsp;&emsp;React中的事件最终都会转换成原生事件,但不会注册到某个真实的DOM元素,而是绑定到根元素,使用事件委托技术进行事件代理,再由一个总的事件监听器管理所有的事件处理程序。 &emsp;&emsp;React中的事件传播只有冒泡这一种形式,而冒泡会先在DOM树中向上传播,一直到根元素,然后再从JSX中触发的React元素开始向它的父元素传播,依次执行绑定的事件处理程序。在下面的示例中,分别为页面中的根元素和\<body>元素,以及组件中的\<div>元素和\<button>元素绑定了点击事件,其中\<div>是\<button>的父元素。 ~~~js document.documentElement.addEventListener("click", function(e) { console.log("html"); }); document.body.addEventListener("click", function(e) { console.log("body"); }); class Btn extends React.Component { handle1(e) { console.log("子元素"); } handle2(e) { console.log("父元素"); } render() { return ( <div onClick={this.handle2}> <button onClick={this.handle1}>提交</button> </div> ); } } ~~~ &emsp;&emsp;当点击提交按钮时,在控制台输出的顺序是“body”、“html”、“子元素”、“父元素”。在React的合成事件中,也会包含一个事件对象(例如handle1()函数中的e参数)。如果要停止冒泡传播,那么可以在相应的事件处理程序中调用e.stopPropagation()。 ## 三、事件对象 &emsp;&emsp;React基于W3C规范对事件对象进行了一层封装,不仅保证了浏览器的兼容性,还提供了一个访问原生事件对象的入口(即调用自身的nativeEvent属性)。注意,在合成事件中阻止元素的默认行为,不能用返回false的方式,得调用事件对象的preventDefault()方法。 &emsp;&emsp;合成事件是被池化的,在执行完事件处理程序后,其参数(即事件对象)的属性将会被丢弃,例如通过定时器异步调用事件对象的type属性,如下所示。 ~~~js class Btn extends React.Component { handle(e) { console.log(e.type); //"click" setTimeout(function() { console.log(e.type); //null }, 0); } render() { return <button onClick={this.handle}>提交</button>; } } ~~~ &emsp;&emsp;点击提交按钮,在控制台首先会输出“click”,然后再输出null。如果想要保持事件对象的属性,那么可以调用它的persist()方法。继续上一个例子,只要在handle()方法中加一句e.persist(),如下代码所示,就能在定时器中输出“click”。 ~~~js handle(e) { console.log(e.type); //"click" setTimeout(function() { console.log(e.type); //"click" }, 0); e.persist(); } ~~~ ## 四、this &emsp;&emsp;事件处理程序中的this不会自动绑定,即默认是没有指向的。在下面的例子中,点击提交按钮,会先触发注册的handle()方法,然后在方法内输出的this的值是undefined。 ~~~js class Btn extends React.Component { handle() { console.log(this); //undefined } render() { return <button onClick={this.handle}>提交</button>; } } ~~~ &emsp;&emsp;有两种方式可以解决this指向的问题,分别是箭头函数和bind()方法。 **1)箭头函数** &emsp;&emsp;将事件处理程序改成箭头函数的形式,就能纠正this的指向,如下代码所示。 ~~~js class Btn extends React.Component { handle() { console.log(this); //Btn实例 } render() { return <button onClick={() => this.handle()}>提交</button>; } } ~~~ &emsp;&emsp;箭头函数是不包含this的,在其内部使用的this是从上一层的作用域中继承而来的,其指向的是函数声明时所在的对象,即当前组件的实例,注意,像上面的\<button>元素那样,直接在其属性中使用箭头函数,会让组件在每次渲染时,创建一个不同的回调函数。 &emsp;&emsp;可以换一种写法,避免每次新建函数,如下代码所示,将箭头函数转移到方法声明的时候。 ~~~js class Btn extends React.Component { handle = () => { console.log(this); //Btn实例 } render() { return <button onClick={this.handle}>提交</button>; } } ~~~ **2)bind()** &emsp;&emsp;为事件处理程序调用bind()方法,同样也能纠正this的指向,如下代码所示。 ~~~js class Btn extends React.Component { handle() { console.log(this); //Btn实例 } render() { return <button onClick={this.handle.bind(this)}>提交</button>; } } ~~~ &emsp;&emsp;函数的bind()方法能创建一个绑定指定对象的函数,在上面的render()方法中,传递给bind()方法的this,其指向的正是当前组件的实例。除了在元素的属性中调用bind()方法之外,还可以将其移到组件的构造函数中,如下所示。 ~~~js class Btn extends React.Component { constructor(props) { super(props); this.handle = this.handle.bind(this); } handle() { console.log(this); //Btn实例 } render() { return <button onClick={this.handle}>提交</button>; } } ~~~ ## 五、事件处理程序的参数 &emsp;&emsp;事件处理程序默认会接收一个事件对象,如果要向其传递额外的参数,那么可以通过两种方式实现。 &emsp;&emsp;第一种是用箭头函数,如下代码所示,显式地将所有参数传递给事件处理程序。 ~~~js class Btn extends React.Component { handle(e, name) { console.log(e, name); } render() { return <button onClick={(e) => this.handle(e, "strick")}>提交</button>; } } ~~~ &emsp;&emsp;第二种是用bind()方法,如下代码所示,事件对象会被隐式的传递过去,并且必须位于事件处理程序参数列表的最后。 ~~~js class Btn extends React.Component { handle(name, e) { console.log(name, e); } render() { return <button onClick={this.handle.bind(this, "strick")}>bind</button>; } } ~~~ ***** > 原文出处: [博客园-React躬行记](https://www.cnblogs.com/strick/category/1455720.html) [知乎专栏-React躬行记](https://zhuanlan.zhihu.com/pwreact) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。