React基础(7)-React中的事件处理

前言

props与state都是用于组件存储数据的一js对象,前者是对外暴露数据接口,后者是对内组件的状态,它们决定了UI界面显示形态,而若想要用户与界面有些交互动做 javascript

也就是web浏览器通知应用程序发生了什么事情,例如:鼠标点击,移动,键盘按下等页面发生相应的反馈,它是用户与文档或者浏览器窗口中发生的一些特定的交互瞬间. 这个时候就须要用事件实现了html

在原生JS操做DOM中,每每有以下方式前端

  • 内联方式(在HTML中直接事件绑定)

<p onclick="alert('关注微信itclanCoder公众号')"></p>java

  • 直接绑定(对象.事件类型 = 匿名函数
// DOM元素对象.事件类型 = 匿名函数  
obj.onclick =function(){})
  • 事件委托监听方式
//对象.addEventListener('事件类型,不带on', 回调函数))对DOM对象进行事件监听处理  
document.addEventListener('click', function(){  
  alert("川川是个全宇宙最帅的小伙子");  
})

而在React中事件处理和内联方式类似,可是却有些不一样react

如何确保函数能够访问组件的属性? web

如何传递参数给事件处理器回调? 怎样阻止函数被调用太快或者太屡次?面试

频繁操做DOM会形成浏览器的卡顿,响应不及时,引发浏览器的重绘重排,从而加剧了浏览器的压力chrome

频繁的调用后台接口,好好的接口被前端玩坏,形成页面空白,崩溃,容易被后端同窗提刀来见npm

既要提高用户体验,又要减小服务器端的开销json

那么本篇就是你想要知道的

若是你想阅读体验更好,可戳连接-React基础(7)-React中的事件处理

React中的事件

在React中事件的绑定是直接写在JSX元素上的,不须要经过addEventListener事件委托的方式进行监听

写法上:

  • 在JSX元素上添加事件,经过on*EventType这种内联方式添加,命名采用小驼峰式(camelCase)的形式,而不是纯小写(原生HTML中对DOM元素绑定事件,事件类型是小写的),无需调用addEventListener进行事件监听,也无需考虑兼容性,React已经封装好了一些的事件类型属性(ps:onClick,onMouseMove,onChange,onFocus)等
  • 使用JSX语法时,须要传入一个函数做为事件处理函数,而不是一个字符串,也就是props值应该是一个函数类型数据,事件函数方法外面得用一个双大括号包裹起来
  • on*EventType的事件类型属性,只能用做在普通的原生html标签上(例如:div,input,a,p等,例如
<div onClick={ 事件处理函数 }></div>

没法直接用在自定义组件标签上,也就是: 下面这样

<Button onClick={事件处理方法}></Button>

这样写是不起做用的,要想解决,也有方法,借用第三方库,styled-component,这个咱们在后续的内容当中单独拿出来说的

  • 不能经过返回false的方式阻止默认行为,必须显示使用preventDefault,以下所示
// 在React中没法经过return false阻止默认事件,下面是错误的写法  
functionhandleClick(){  
  // 逻辑代码  
  returnfalse;  
}  
// 正确的写法,应该用preventDefault去阻止默认事件  
functionhandleClick(event){  
  event.preventDefault();  
}

event(事件)对象

事件是web浏览器通知应用程序发生的什么事情,例如:鼠标点击,移动,键盘按下等

它并非javascript对象,可是由事件触发的事件处理函数接收携带的事件对象参数(event),它会记录这个事件的一些详细的具体信息

<a href="#" onClick = { this.handleLink} >连接</a>

handleLink(event){
 event.preventDefault();
 console.log(event);
}

event会记录该事件对象的信息,以下图所示

当给DOM元素绑定了事件处理函数的时候,该函数会自动的传入一个event对象,这个对象和普通的浏览器的对象记录了当前事件的属性和方法

在React中,event对象并非浏览器提供的,你能够将它理解为React的事件对象,由React将原生浏览器的event对象进行了封装,对外提供一公共的API接口,无需考虑各个浏览器的兼容性

与原生浏览器处理事件的冒泡(event.stopPropatation),阻止默认行为(event.preventDefault`)使用同样

this绑定性能比较

在上一节中已经对this的绑定进行了学习,在一次拿出来,说明它的重要性

一般在对JSX元素绑定事件监听处理函数时,针对this的绑定,将事件处理函数绑定到当前组件的实例上:以获取到父组件传来的props

如下几种方式能够确保函数能够访问组件属性

  • 在构造函数中绑定 在constructor中进行this坏境的绑定,初始化事件监听处理函数
classButtonextendsComponent{  
  constructor(props){  
  super(props);  
  // 在构造器函数中进行this坏境的绑定  
  this.handleBtnClick =this.handleBtnClick.bind(this);  
}  
  
render(){  
return(  
  <div>  
   <buttononClick={this.handleBtnClick}>按钮</button\>  
  </div>  
);  
}  
  
handleBtnClick(){  
  console.log(this); // 会输出Button组件  
}  
}

当在JSX上进行事件监听绑定的时候,对于JSX回调函数中的this,因为Es6中的class的方法默认不会绑定this,若是你不进行this的坏境绑定,忘记绑定事件处理函数,并把它传给事件方法(上面是onClick),那么this的值是undefined

解决这个问题:

  • 一种是如上面的在构造器函数中进行this坏境的绑定,这种方式是React官方推荐的,也是性能比较好的
  • 第二种方式是直接在JSX上,Render中经过bind方法进行this的绑定
<button onClick={this.handleBtnClick.bind(this) }>按钮</button>

使用这种bind直接的绑定,每次渲染组件,都会建立一个新的函数,通常而言,这种写法也没什么问题,可是若是该回调函数做为prop值传入子组件时,这些组件就会进行额外的从新渲染,会影响性能,这与使用箭头函数一样存在这样的问题

解决办法:

  • 在构造器函数中进行绑定,如上所示:
  • 利用class fields(类字段)语法
classButtonextendsComponent{  
  
// 类字段的形式进行绑定,函数表达式  
handleClick =()=>{  
  alert("学习React基础内容");  
}  
render(){  
return(  
  <div>  
    <buttononClick={this.handleBtnClick}>按钮</button\>  
  </div>  
);  
}  
}

若是不用类字段语法,能够在回调中使用箭头函数,这与它是等价的

classButtonextendsComponent{  
  
handleClick()  
 alert("学习React基础内容");  
}  
render(){  
 return(  
  <div>  
    <buttononClick\={() =>{ this.handleBtnClick } }>按钮</button>  
  </div>  
);  
}  
}

此方法与直接在Render函数中使用bind绑定this坏境同样存在性能问题,当该事件处理函数做为prop传入子组件,一定会引发Render函数的渲染

因此出于性能的考虑,将this的绑定放在constructr函数中或者用类字段的语法来解决这种性能瓶颈问题

向事件处理程序中传递参数

在循环操做列表中,有时候要实现某些操做,咱们须要向事件处理函数传递一些额外的参数,好比说:索引,要删除哪一行的ID 经过如下两种方式均可以向事件处理函数传递参数

<button onClick = {this.handleBtnDelete.bind(this,id)}>删除</button>  
或者  
<button onClick = { (e) =>this.handleDelete(id, e) }>删除</button>

以下以一个删除list的例子,效果以下,代码所示

importReact, { Fragment, Component }from'react';  
importReactDOMfrom'react-dom';  
  
classListextendsComponent{  
  constructor(props){  
  super(props);  
  
  const{ list } =this.props;  
  this.state = {  
    list: list  
  }  
  
}  
  
render(){  
  const{ list } =this.state;  
  return(  
   <Fragment>  
     <ul>  
       {  
        // list.map((item, index) =><lionClick={this.handleDelete.bind(this,index)}key={index}>{ item }</li\>)  
        list.map((item, index) =><lionClick={(e) =>this.handleDelete(index, e)} key={index}>{ item }</li>)  
       }  
     </ul>  
   </Fragment>  
);  
}  
  
handleDelete(index, e){  
   console.log(e)  
   // 拷贝state的一个副本,不要直接的去更改state,在React中,不容许对state作任何改变  
   constlist = [...this.state.list];  
   list.splice(index,1);  
  
   this.setState(()=>({  
     list: list  
   }))  
}  
  
}  
  
constlistData = ["itclanCoder","川川","chrome","Firefox","IE"]  
  
constcontainer =document.getElementById('root');  
  
ReactDOM.render(<Listlist={listData} />, container);

在上面代码中,分别在render函数中绑定(Function.proptype.bind)和利用箭头函数包裹事件处理器,向事件监听处理函数传递参数,都是等价的

<button onClick = {this.handleBtnClick(this, id)}></button>  
// 等价于  
<button onClick = { () =>this.handleBtnClick(id) }></button>

若使用箭头函数,React的事件对象会被做为第二个参数传递,并且也必须显示的传递进去

而经过bind的方式,事件对象以及更多的参数将会被隐式的传递进去

在render函数中直接的经过bind方法的绑定,会在每次组件渲染时都会建立一个新的函数,它会影响性能:最好是放在constructor函数中进行this坏境的绑定,由于constructor函数只会执行一次

constructor(props){  
  super(props);  
  // 事件监听处理函数,this坏境的绑定  
  this.handleDelete =this.handleDelete.bind(this);  
}

解决事件处理函数每次被重复渲染的问题

在Es5中,当调用一个函数时,函数名每每要加上一个圆括号,而在JSX 中给React元素绑定事件处理函数时,一个不当心,就习惯给加上了的

这就会形成,每次组件渲染时,这个函数都会被调用,会引发没必要要的render函数渲染,将会引发性能问题

应当确保在传递一个函数给组件时,没有当即调用这个函数,以下所示

render(){  
  return(  
   <buttononClick={this.handleClick()}>button</button>  
  );  
}

正确的作法是,应该传递该事件函数自己(不加括号),以下所示

render(){  
 <button onClick = {this.handleClick }>button</button>  
}

下面介绍本节的重点,听过函数节流,防抖,但不必定就真的就懂了

如何阻止函数调用太快(函数节流)或者太屡次(函数防抖)

有时候,当用户频繁的与UI界面操做交互时,例如:窗口调整(触发resize),页面滚动,上拉加载(触发scroll),表单的按钮提交,商城抢购疯狂的点击(触发mousedown),而实时的搜索(keyup,input),拖拽等

当你频繁的触发用户界面时,会不停的触发事件处理函数,换而言之,当出现连续点击,上拉加载,实时搜索,对DOM元素频繁操做,请求资源加载等耗性能的操做,可能致使界面卡顿,浏览器奔溃,页面空白等状况

而解决这一问题的,正是函数节流与函数防抖
函数节流

定义: 节约(减小)触发事件处理函数的频率,连续每隔必定的时间触发执行的函数,它是优化高频率执行一段js代码的一种手段

特色: 无论事件触发有多频繁,都会保证在规定的间隔时间内真正的执行一次事件处理函数

应用场景: 经常使用于鼠标连续屡次点击click事件,鼠标移动mousemove,拖拽,窗口尺寸改动(resize),鼠标滚轮页面上拉(onScroll),上拉刷新懒加载

原理: 经过判断是否达到必定的时间来触发函数,若没有规定时间则使用计时器进行延迟,而下一次事件则会从新设定计时器,它是间隔时间执行

一般与用户界面高频的操做有:

  • 鼠标滚轮页面上拉(onScroll),下拉刷新懒加载
  • 窗口尺寸改动(onresize)
  • 拖拽

如果高频操做,若不进行必定的处理,必然会形成屡次数据的请求,服务器的压力,这样代码的性能是很是低效的,影响性能,下降这种频繁操做的一个重要的手段,就是下降频率,经过节流控制,也就是让核心功能代码在必定的时间,隔多长时间内执行一次

节流就是保证一段时间内只执行一次核心代码

你能够联想生活中节约用水(三峡大坝设置不少水闸)的例子:

高频事件就像是一个大开的水龙头,水流源源不断的大量流出,就像代码在不断的执行,若不加以控制,就会形成资源的一种浪费

对应页面中的,如果表单中连续点击提交按钮,监听滚动事件,连续下拉加载等请求服务器的资源

要节流,拧紧水龙头,要它的流水频率下降,每隔一段时间滴一滴水的,从而节省资源

在代码中的体现就是:设置必定时器,让核心功能代码,隔间段的去执行

下面是一个鼠标滚轮,节流操做实现:相似连续操做的,都是如此,连续点击按钮,上拉加载

节流方式一:时间戳+定时器

/\*  
throttle1函数,节流实现方式1:时间戳+定时器  
\* @params method,duration 第一个参数为事件触发时的真正要执行的函数  
\* 第二个参数duration表示为定义的间隔时间  
\*  
\* 原理:经过判断是否达到必定的时间来触发函数,若没有规定时间则使用计时器进行延迟,而下一次事件则会从新设定计时器,它是间隔时间执行,无论事件触发有多频繁,都会保证在规定内的事件必定会执行一次真正事件处理函数  
\*  
\* \*/  
functionthrottle1(method, duration){  
  vartimer =null;  
  varprevTime =newDate();// 以前的时间  
  returnfunction(){  
   varthat =this,  
      currentTime =newDate(),// 获取系统当前时间  
      resTime = currentTime - prevTime;// 时间戳  
     // 打印本次当前的世间和上次世间间隔的时间差  
      console.log("时间差", resTime);  
     // 当前距离上次执行时间小于设置的时间间隔  
     if(resTime < duration) {  
      // 清除上次的定时器,取消上次调用的队列任务,从新设置定时器。这样就能够保证500毫秒秒内函数只会被触发一次,达到了函数节流的目的  
       clearTimeout(timer);  
       timer = setTimeout(function(){  
       prevTime = currentTime;  
       method.apply(that);  
     }, duration)  
}else{// 当前距离上次执行的时间大于等于设置的时间时,直接执行函数  
  // 记录执行方法的时间  
  prevTime = currentTime;  
  method.apply(that);  
}  
  
}  
}  
  
// 事件触发的方法(函数),函数节流1  
functionhandleJieLiu1(){  
  console.log("节流方式1");  
}  
  
varhandleJieLiu1 = throttle1(handleJieLiu1,500);  
document.addEventListener('mousewheel', handleJieLiu1);

节流方式二:

/\*  
\* throttle2函数节流实现方式2:重置一个开关变量+定时器  
\* @params method,duration形参数与上面的含义一致  
\* @return 返回的是一个事件处理函数  
\*  
\* 在throttle2执行时定义了runFlag的初始值,经过闭包返回一个匿名函数做为事件处理函数,  
\*  
\* 在返回的函数内部判断runFlag的状态并肯定执行真正的函数method仍是跳出,每次执行method后会更改runFlag的状态,经过定时器在durtion该规定的间隔时间内重置runFlag锁的状态  
\*  
\*/  
functionthrottle2(method, duration){  
   // 当前时间间隔内是否有方法执行,设置一个开关标识  
   varrunFlag =false;  
   // 返回一个事件处理函数  
   returnfunction(e){  
     // 判断当前是否有方法执行,有则什么都不作,若为true,则跳出  
     if(runFlag){  
       returnfalse;  
      }  
     // 开始执行  
      runFlag =true;  
      // 添加定时器,在到达时间间隔时重置锁的状态  
      setTimeout(function(){  
          method(e);  
          // 执行完毕后,声明当前没有正在执行的方法,方便下一个时间调用  
          runFlag =false;  
      }, duration)  
   }  
}  
// 事件触发的方法(函数),函数节流2  
functionhandleJieLiu2(){  
   console.log("节流方式2");  
}  
varhandleJieLiu2 = throttle2(handleJieLiu2,500);  
document.addEventListener('mousewheel', handleJieLiu2);

上面两种实现函数节流的方式均可以达到防止用户频繁操做而引发重复请求资源的

具体效果以下所示

从上面的效果示例当中,当鼠标滚轮不断滚动时,事件处理函数的执行顺序不同

当给一个大范围的时间内,好比:1小时内,每几分钟执行一次,超过一小时不在执行,推荐使用第一种函数节流的方式

若是仅仅要求间隔必定时间执行一次,推荐使用第二种函数节流的方式

函数防抖

定义:防止抖动,重复的触发,频繁操做,核心在于,延迟事件处理函数的执行,必定时间间隔内只执行最后一次操做,例如:表单屡次提交,推荐使用防抖

换句话说,也就是当连续触发事件时并无执行事件处理函数,只有在某一阶段连续触发的最后一次才执行,它遵循两个条件

  • 必需要等待一段时间
  • 上一次触发的时间间隔要大于设定值才执行

特色: 某段时间内只执行一次 在生活中,你能够想象公交司机等人上车后,才出站同样

应用场景: 常应用于输入框事件keydown,keyup,搜索联想查询,只有在用户中止键盘输入后,才发送Ajax请求

原理: 它是维护一个计时器,规定在duration(延迟)必定的时间后,触发事件处理函数,可是在duration时间内再次触发的话,都会清除当前的timer定时器从新计时,这样一来,只有最后一次操做事件处理函数才会被真正的触发

具体代码以下所示:

/\*  
\* 函数防抖  
\* 例如:假定时间间隔时500ms,频繁不一样的操做5s,且每两次执行时间小于等于间隔500ms  
\* 那么最后只执行了1次,也就是每一次执行时都结束上一次的执行  
\* @params method,duration,与上面一致  
\*  
\* 原理:它是维护一个计时器,规定在duration时间后出发时间处理函数,可是在duration时间内再次出发的化,都会清除当前的timer从新计时,这样一来,只有最后一次操做事件处理函数才被真正的触发  
\*  
\* 通常用于输入框事件,经常使用场景就是表单的搜索或者联想查询,若是不使用防抖会连续发送请求,增长服务器的压力,使用防抖后,会在用户输入要查询的关键词后才发送请求,百度搜索就是这么实现的  
\*  
\*  
\*/  
functiondebounce(method, duration){  
  vartimer =null;  
  returnfunction(){  
    varthat =this,  
    args =arguments;  
    // 在本次调用之间的一个间隔时间内如有方法在执行,则终止该方法的执行  
    if(timer) {  
     clearTimeout(timer);  
    }  
    // 开始执行本次调用  
   timer = setTimeout(function(){  
     method.apply(that,args);  
    }, duration)  
  
}  
  
}  
// 事件触发的方法(函数),防抖  
functionhandleFangDou(){  
   console.log("函数的防抖",newDate());  
}  
varhandleFangDou = debounce(handleFangDou,500);  
varoInput =document.querySelector("#input");// 获取input元素  
oInput.addEventListener('keyup',handleFangDou);

具体效果以下所示:

如上输入框效果所示,每当输入框输入值后,当键盘弹起时,执行事件处理函数,而不该该是键入内容时都触发一次事件处理函数

同理,搜索引擎,表单联想查询功能时,不是根据用户键入的字母,数字,内容同时进行Ajax数据请求的,若是每键入一个字母都触发一次数据请求,那就很是耗性能了的

应当是用户中止输入的时候才去触发查询请求,这个时候就用到函数防抖了的

表单的屡次提交,百度搜索等都是用防抖实现的

小结:

共同点: 都是解决频繁操做触发事件处理函数,引发页面卡顿,不流畅等性能问题,都是经过设置延时计时器逻辑来提高性能,以减小http请求次数,节约请求资源

不一样点:函数节流,间隔时间内执行事件处理函数,而函数防抖,必定时间间隔内只执行最后一次操做

那么在React中,又是如何实现函数的节流,函数的防抖的?

在React中借用了一个loadsh.throttle的库实现函数的节流

首先你要在命令行终端下经过npm或者cnpm安装这个库

cnpm i -S lodash.throttle

而后在你编写的React组件内引入,调用一个throttle的函数,这个throttle接收两个参数,第一个参数是要触发的事件处理函数,第二个是延迟的时间,隔多少秒调用一次

下面是函数的节流代码,给定时间内被调用不能超过一次,对点击click事件处理器,使每秒钟只能调用一次

importReact, { Fragment, Component }from'react';  
importReactDOMfrom'react-dom';  
importthrottlefrom'lodash.throttle';// 引入lodash.throttle库  
  
classLoadMoreButtonextendsComponent{  
  constructor(props) {  
  super(props);  
  
   this.state = {  
     tip:'',  
     trigerTimes:1  
}  
  
  this.handleClick =this.handleClick.bind(this);  
  this.handleClickThrottled = throttle(this.handleClick,1000);// 将触发事件处理函数做为第一个参数传入,第二个参数为间隔的时间,这里是1秒  
}  
  
componentWillUnmount() {  
  this.handleClickThrottled.cancel();  
}  
  
render() {  
 return(  
  <Fragment>  
    <div><buttononClick={this.handleClickThrottled}>Load More</button></div>  
    <div>{ this.state.tip }</div>  
  </Fragment>  
  
)  
}  
  
handleClick() {  
  this.setState({  
     tip:\`加载按钮触发了:${this.state.trigerTimes }次\`,  
     trigerTimes:this.state.trigerTimes+1  
})  
}  
}  
  
classLoadextendsComponent{  
  constructor(props){  
  super(props);  
  
}  
  
render(){  
return(  
 <Fragment>  
   <LoadMoreButton>  
 </Fragment>  
);  
}  
  
}  
constcontainer =document.getElementById('root');  
  
ReactDOM.render(<Load/>, container);

效果以下所示

若是你不使用lodash.throttled第三方库实现函数的节流,一样,本身单独封装一个throttled实现函数节流也是能够的,例如:

importReact, { Fragment, Component }from'react';  
importReactDOMfrom'react-dom';  
  
  
classLoadMoreButtonextendsComponent{  
  constructor(props) {  
   super(props);  
  
   this.state = {  
      tip:"",  
      trigerTimes:1  
  }  
  
this.handleLoadTime =this.handleLoadTime.bind(this);  
this.handleClick =this.handleClick.bind(this);  
this.handleClickThrottled =this.throttle(this.handleClick,1000);// 将触发事件处理函数做为第一个参数传入,第二个参数为间隔的时间,这里是1秒  
  
}  
  
  
render() {  
  
return(  
<Fragment>  
  <div><buttononClick={this.handleClickThrottled}>Load More</button></div>  
   <div>{ this.state.tip }</div>  
</Fragment>  
)  
}  
  
handleLoadTime(){  
// this.setState((prevState) => ({  
// tip: `加载按钮触发了: ${prevState.trigerTimes}次`,  
// trigerTimes: prevState.trigerTimes+1  
// }))  
// 等价于下面的  
  this.setState({  
    tip:`加载按钮触发了:${this.state.trigerTimes }次`,  
    trigerTimes:this.state.trigerTimes+1  
})  
}  
// 事件处理函数  
handleClick() {  
  this.handleLoadTime();  
}  
  
// 核心函数节流代码实现  
throttle(method, duration){  
  // 当前时间间隔内是否有方法执行,设置一个开关标识  
   varrunFlag =false;  
   // 返回一个事件处理函数  
    returnfunction(e){  
     // 判断当前是否有方法执行,有则什么都不作,若为true,则跳出  
     if(runFlag){  
       returnfalse;  
      }  
      // 开始执行  
     runFlag =true;  
      // 添加定时器,在到达时间间隔时重置锁的状态  
     setTimeout(function(){  
         method(e);  
      // 执行完毕后,声明当前没有正在执行的方法,方便下一个时间调用  
      runFlag =false;  
     }, duration)  
}  
}  
}  
  
classLoadextendsComponent{  
  constructor(props){  
    super(props);  
  
}  
  
render(){  
return(  
  <Fragment>  
    <LoadMoreButton/>  
  </Fragment>  
);  
}  
}  
  
constcontainer =document.getElementById('root');  
  
ReactDOM.render(<Load/>, container);

你能够试着不加第三方库lodash.throttled中的throtte函数以及不封装throttle函数,你会发现,当你点击按钮时,你连续点多少次,它会不断的触发事件处理函数,若是是一个表单提交按钮,使用函数的节流就很好的优化了代码了

不加函数节流的效果:以下所示:

假如这是一个表单的提交按钮,你点击多少次,就向服务器请求多少次,这显然是有问题的,若是你用函数的节流就很好解决这个问题

上面说完了React的函数节流,那么函数防抖又怎么实现呢?一样,React能够借助一个第三方库loadsh.debounce来实现

你仍然先要在终端下经过npm或者cnpm或yarn的方式安装第三方库

npm i -S loadsh.debounce  
或者  
cnpm install -S loadsh.debounce

有没有安装上,能够在根目录下查看pageckage.json中的dependencies依赖里面有没有loadsh.debounce

下面看一个输入框,校验手机号的例子: 这在一些邮箱注册,快捷登陆等表单处是一个很常见的应用场景

没有使用函数防抖 示例代码以下所示

importReact, { Fragment, Component }from'react';  
importReactDOMfrom'react-dom';  
  
  
classSearchBoxextendsComponent{  
  constructor(props){  
    super(props)  
    this.state = {  
     tip:null,  
     trigerTimes:1  
  }  
  
   this.handleChange =this.handleChange.bind(this);  
}  
  
handleChange(e){  
  if(e.target.value){  
    this.setState({  
    tip:null  
})  
}  
  
}  
  
handleKeyUp =(e) =>{  
  if(e.target.value){  
  this.isPhoneLegal(e.target.value)// 对用户输入进行判断  
}  
  
}  
isPhoneLegal =(phone) =>{  
  constphoneRegexp =/^1(\[38\]\\d|5\[0-35-9\]|7\[3678\])\\d{8}$/  
  const{ trigerTimes } =this.state  
  if(phoneRegexp.test(phone)) {  
   this.setState({  
      tip:`手机号符合规则!`,  
      trigerTimes:0  
   })  
  }else{  
   this.setState({  
     tip:`手机号有误, 触发了:${trigerTimes}次`,  
     trigerTimes: trigerTimes +1  
})  
}  
  
// 这里发送Ajax请求  
}  
  
render() {  
return(  
  <Fragment>  
   <div><inputonChange={this.handleChange}onKeyUp={this.handleKeyUp}placeholder="请输入手机号"/></div>  
   <div>  
     {this.state.tip}  
   </div>  
 </Fragment>  
)  
}  
  
}  
  
class Search extends Component{  
render(){  
  return (  
   <Fragment>  
     <SearchBox>  
   </Fragment>  
);  
}  
}  
  
const container = document.getElementById('root');  
  
ReactDOM.render(<Search/>, container);


未使用防抖时,每次键盘keyup弹起一次,就会触发一次,用户未输入完成就提示输入有误,这种体验不是很好 

换而言之,若是每次键盘弹起时,都发送Ajax请求,这种思路本是没错的,可是如果间隔时间很短,连续输入,老是频繁的发送Ajax请求,那就形成页面卡顿,服务器端的压力了

正常的效果 示例效果以下所示:应该等键盘内容输入完以后,才触发事件处理函数

下面是使用了debounce函数进行函数防抖 示例代码以下所示

importReact, { Fragment, Component }from'react';  
importReactDOMfrom'react-dom';  
//import throttle from 'lodash.throttle'; // 函数节流  
importdebouncefrom'lodash.debounce';// 函数防抖,引入loadash.debounce库  
  
classSearchBoxextendsComponent{  
  constructor(props){  
   super(props)  
   this.state = {  
     tip:null,  
     trigerTimes:1  
   }  
  this.handleChange =this.handleChange.bind(this);  
  this.isPhoneLegal = debounce(this.isPhoneLegal,1000) // 此处调用debounce函数,第一个参数为事件处理函数,第二个参数为延迟的时间间隔,这里是1s  
}  
  
componentWillUnmount(){  
  this.isPhoneLegal.cancel();  
}  
  
handleChange(e){  
  if(e.target.value){  
    this.setState({  
    tip:null  
})  
}  
  
}  
  
handleKeyUp =(e) =>{  
  if(e.target.value){  
    this.isPhoneLegal(e.target.value)// 对用户输入进行判断  
  }  
  
}  
isPhoneLegal =(phone) =>{  
  constphoneRegexp =/^1(\[38\]\\d|5\[0-35-9\]|7\[3678\])\\d{8}$/  
  const{ trigerTimes } =this.state  
  if(phoneRegexp.test(phone)) {  
   this.setState({  
     tip:`手机号符合规则!`,  
      trigerTimes:0  
   })  
  }else{  
   this.setState({  
     tip:`手机号有误, 触发了:${trigerTimes}次`,  
     trigerTimes: trigerTimes +1  
})  
}  
  
// 这里发送Ajax请求  
}  
  
render() {  
return(  
<Fragment>  
   <div><inputonChange\={this.handleChange}onKeyUp={this.handleKeyUp}placeholder="请输入手机号"/></div\>  
   <div>  
     {this.state.tip}  
   </div>  
</Fragment>  
)  
}  
  
}  
  
class Search extends Component{  
render(){  
return (  
<Fragment>  
   <SearchBox>  
</Fragment>  
);  
}  
}  
  
const container = document.getElementById('root');  
  
ReactDOM.render(<Search/>, container);

若是你不使用lodash.debounce这个库提供的debounce函数进行防抖处理,本身用原生的方法封装一个debounce函数也是能够的

上面有介绍的 代码以下所示:你只需把对事件处理函数this坏境绑定处的deboucunce更改一下便可,其余代码跟之前同样

this.isPhoneLegal =this.debounce(this.isPhoneLegal,1000)

注意此时debounce函数是放在这个searchBox组件内的,若是该debounce函数放在组件外部,是直接用function声明式定义的,直接调用debouce函数名便可,这里要稍稍注意下区别,对于这种经常使用的函数,能够单独把它封装到一个文件里去也是能够的

收集成本身经常使用库当中,避免这种防抖,节流函数分散在各个文件,处处都是的,如下是debounce防抖函数的封装

// 本身封装一个debounce函数用于防抖  
debounce(method, duration) {  
vartimer =null;  
/\*return function(){  
var that = this,  
args = arguments;  
// 在本次调用之间的一个间隔时间内如有方法在执行,则终止该方法的执行  
if(timer) {  
clearTimeout(timer);  
}  
// 开始执行本次调用  
timer = setTimeout(function(){  
method.apply(that,args);  
}, duration)  
  
}\*/  
// 上面的return匿名函数能够用Es6的箭头函数,如下写法与上面等价,最简洁的写法,可是没有上面的代码好理解  
return(...args) =>{  
  clearTimeout(timer);  
  timer = setTimeout(()=>method(...args), duration)  
}  
  
}

固然对于上面的代码,仍是能够优化一下的,对于回调函数,在Es6中,经常使用于箭头函数来处理,这样会省去很多麻烦 例如:this的指向问题 以下所示:debouce函数最简易的封装

你也能够把上面的定时器初始值放在debouce函数做为第三个形参数设置,也是能够的

debounce(method, duration, timer =null) {  
  return(...args) =>{  
    clearTimeout(timer);  
    timer = setTimeout(()=>{  
    method(...args)  
   }, duration)  
  }  
  
}

若是本身封装throttledebounce函数,能够单独封装到一个文件对外暴露就能够了,在须要用它们的地方,经过import引入便可,在代码中直接调用就能够 在根目录下(以你本身的为准)建立一个throttle.js 经过export default 暴露出去

/\*  
\* @authors 川川 (itclancode@163.com)  
\* @ID suibichuanji  
\* @date 2019-08-31 21:08:17  
\* @weChatNum 微信公众号:itclancoder  
@desc 封装节流函数  
\* @param method,duration:method事件处理函数,duration:间隔的时间  
\* @return 匿名函数  
\* 原理: 经过判断是否达到必定的时间来触发函数,  
\* 若没有规定时间则使用计时器进行延迟,而下一次事件则会从新设定计时器  
\* 它是间隔时间执行,无论事件触发有多频繁  
\* 都会保证在规定内的事件必定会执行一次真正事件处理函数  
\*  
\*/  
functionthrottle(method, duration){  
  vartimer =null;  
  varprevTime =newDate();// 以前的时间  
  returnfunction(){  
    varthat =this,  
       currentTime =newDate(),// 获取系统当前时间  
       resTime = currentTime - prevTime;// 时间戳  
      // 打印本次当前的世间和上次世间间隔的时间差  
     console.log("时间差", resTime);  
    // 当前距离上次执行时间小于设置的时间间隔  
    if(resTime < duration) {  
      // 清除上次的定时器,取消上次调用的队列任务,从新设置定时器。这样就能够保证500毫秒秒内函数只会被触发一次,达到了函数节流的目的  
      clearTimeout(timer);  
      timer = setTimeout(function(){  
         prevTime = currentTime;  
         method.apply(that);  
      }, duration)  
   }else{// 当前距离上次执行的时间大于等于设置的时间时,直接执行函数  
    // 记录执行方法的时间  
    prevTime = currentTime;  
    method.apply(that);  
}  
  
}  
}  
exportdefaultthrottle;

而后在须要使用函数节流文件中引入,以下所示:其余代码省略

importthrottlefrom'./throttle';  
  
// 在组件的constructor内初始化,this坏境绑定处进行调用  
this.handleClickThrottled = throttle(this.handleClick,1000);

同理,如果本身封装debounce函数用于防抖,应把它单独的抽离出去封装成一个函数,经过export 对外暴露,供其余地方调用

/\*\*  
\*  
\* @authors 川川 (itclancode@163.com)  
\* @ID suibichuanji  
\* @date2019-08-3121:10:17   
\* @version $Id$  
\* @description 函数防抖  
\* @param { method, duration} \[method是事件处理函数,duration是延迟时间\]  
\* 原理:它是维护一个计时器,规定在duration时间后出发时间处理函数  
\* 可是在duration时间内再次出发的化,都会清除当前的timer从新计时  
\* 这样一来,只有最后一次操做事件处理函数才被真正的触发  
\*  
\* 通常用于输入框事件,经常使用场景就是表单的搜索或者联想查询,  
\* 若是不使用防抖会连续发送请求,增长服务器的压力  
\* 使用防抖后,会在用户输入要查询的关键词后才发送请求,百度搜索就是这么实现的  
\*/  
functiondebounce(method, duration){  
  vartimer =null;  
  returnfunction(){  
   varthat =this,  
      args =arguments;  
     // 在本次调用之间的一个间隔时间内如有方法在执行,则终止该方法的执行  
    if(timer) {  
     clearTimeout(timer);  
    }  
   // 开始执行本次调用  
   timer = setTimeout(function(){  
      method.apply(that,args);  
   }, duration)  
  
}  
  
}  
  
exportdefaultdebounce;

小结:

React中如何实现函数的节流和防抖?

  • 引用lodash.throttle第三方库的throttle函数用于节流
  • 本身封装throttle函数用于节流
  • 引用lodash.debounce第三方库的debounce函数用于防抖
  • 本身封装debounce函数用于防抖

结语

整篇文章到这里就结束了,若是你可以坚持读完或者看完视频,相信对于React中的事件处理有了必定的理解和认识,光看仍然是迷迷迷糊的,似懂非懂,一手写起来,就卡壳..文字讲千百遍,不如代码撸一遍

主要从介绍React事件开始,event(事件)对象,this绑定性能比较,向事件处理程序中传递参数,到最后的如何阻止函数调用太快(函数节流,两种方式)或者太屡次(函数防抖),分别用原生JS以及React中的第三方库实现

对于函数的节流与防抖是前端提高性能的手段,虽然就几行代码,可是面试时,常问不衰,让你手写,不少时候,拍拍胸脯,不借助搜索引擎,还真不必定能立马写得出来

在实际的开发中,函数的节流与函数防抖也是用得比较频繁的,可见它的重要性不言而喻

若是您喜欢,以为文章对你有所启发

相关文章
相关标签/搜索