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中的第三方庫實現

對於函數的節流與防抖是前端提高性能的手段,雖然就幾行代碼,可是面試時,常問不衰,讓你手寫,不少時候,拍拍胸脯,不借助搜索引擎,還真不必定能立馬寫得出來

在實際的開發中,函數的節流與函數防抖也是用得比較頻繁的,可見它的重要性不言而喻

若是您喜歡,以爲文章對你有所啓發

相關文章
相關標籤/搜索