1. 程式人生 > >React 深入系列5:事件處理

React 深入系列5:事件處理

箭頭函數 com 如果 發布 web click 系列 額外 新的

文:徐超,《React進階之路》作者

授權發布,轉載請註明作者及出處


React 深入系列5:事件處理

React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。

Web應用中,事件處理是重要的一環,事件處理將用戶的操作行為轉換為相應的邏輯執行或界面更新。在React中,處理事件響應的方式有多種,本文將詳細介紹每一種處理方式的用法、使用場景和優缺點。

使用匿名函數

先上代碼:

//代碼1
class MyComponent extends React.Component {
  render() {
    return (
      <button onClick={()=>{console.log(‘button clicked‘);}}>
        Click
      </button>
    );
  }
}

點擊Button的事件響應函數是一個匿名函數,這應該是最常見的處理事件響應的方式了。這種方式的好處是,簡單直接。哪裏需要處理事件響應,就在哪裏定義一個匿名函數處理。代碼1中的匿名函數使用的是箭頭函數,我們也可以不使用箭頭函數:

//代碼2
class MyComponent extends React.Component {
  render() {
    return (
      <button onClick={function(){console.log(‘button clicked‘);}}>
        Click
      </button>
    );
  }
}

雖然代碼2的運行效果和代碼1相同,但實際項目中很少見到代碼2的這種寫法。這是因為箭頭函數解決了this綁定的問題,可以將函數體內的this綁定到當前對象,而不是運行時調用函數的對象。如果響應函數中需要使用this.state,那麽代碼2就無法正常運行了。所以項目中一般直接使用箭頭函數定義的匿名函數作為事件響應。

使用匿名函數的缺點是:當事件響應邏輯比較復雜時,匿名函數的代碼量會很大,會導致render函數變得臃腫,不容易直觀地看出組件最終渲染出的元素結構。另外,每次render方法調用時,都會重新創建一個匿名函數對象,帶來額外的性能開銷,當組件的層級越低時,這種開銷就越大,因為任何一個上層組件的變化都可能會觸發這個組件的render方法。當然,在大多數情況下,這點性能損失是可以不必在意的。

使用組件方法

代碼如下:

//代碼3
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {number: 0};
    this.handleClick = this.handleClick.bind(this); // 手動綁定this
  }

  handleClick() {
    this.setState({
      number: ++this.state.number
    });
  }
  
  render() {
    return (
      <div>
        <div>{this.state.number}</div>
        <button onClick={this.handleClick}>
          Click
        </button>
      </div>
    );
  }
}

點擊Button的事件響應函數是組件的方法:handleClick。這種方式的好處是:每次render方法的調用,不會重新創建一個新的事件響應函數,沒有額外的性能損失。但是,使用這種方式要在構造函數中為作為事件響應的方法(handleClick),手動綁定this: this.handleClick = this.handleClick.bind(this),這是因為ES6 語法的緣故,ES6 Class 的方法默認不會把this綁定到當前的實例對象上,需要我們手動綁定。每次都手動綁定this是不是有點繁瑣?好吧,讓我們來看下一種方式。

使用屬性初始化語法

使用ES7的屬性初始化語法( property initializers ),代碼可以這樣寫:

//代碼4
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {number: 0};
  }

  handleClick = () => {
    this.setState({
      number: ++this.state.number
    });
  }
  
  render() {
    return (
      <div>
        <div>{this.state.number}</div>
        <button onClick={this.handleClick}>
          Click
        </button>
      </div>
    );
  }
}

這樣一來,再也不用手動綁定this了。但是你需要知道,這個特性還處於試驗階段,默認是不支持的。如果你是使用官方腳手架Create React App 創建的應用,那麽這個特性是默認支持的。你也可以自行在項目中引入babel的transform-class-properties插件獲取這個特性支持。

事件響應函數的傳參問題

事件響應函數默認是會被傳入一個事件對象Event作為參數的。如果想傳入其他參數給響應函數應該怎麽辦呢?

使用第一種方式的話很簡單,直接使用新參數:

//代碼5
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [1,2,3,4],
      current: 1
    };
  }
  
  handleClick(item,event) {
    this.setState({
      current: item
    });
  }

  render() {
    return (
      <ul>
        {this.state.list.map(
          (item)=>(
            <li className={this.state.current === item ? ‘current‘:‘‘} 
            onClick={(event) => this.handleClick(item, event)}>{item}
            </li>
          )
        )}
      </ul>
    );
  }
}

onClick的響應函數中,方法體內可以直接使用新的參數item。

使用第二種方式的話,可以把綁定this的操作延遲到render中,在綁定this的同時,綁定額外的參數:

//代碼6
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [1,2,3,4],
      current: 1
    };
  }
  
  handleClick(item) {
    this.setState({
      current: item
    });
  }

  render() {
    return (
      <ul>
        {this.state.list.map(
          (item)=>(
            <li className={this.state.current === item ? ‘current‘:‘‘} 
            onClick={this.handleClick.bind(this, item)}>{item}
            </li>
          )
        )}
      </ul>
    );
  }
}

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

//代碼7
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [1,2,3,4],
      current: 1
    };
  }
  
  handleClick = (item) =>  {
    this.setState({
      current: item
    });
  }

  render() {
    return (
      <ul>
        {this.state.list.map(
          (item)=>(
            <li className={this.state.current === item ? ‘current‘:‘‘} 
            onClick={this.handleClick.bind(undefined, item)}>{item}
            </li>
          )
        )}
      </ul>
    );
  }
}

不過這種方式就有點雞肋了,因為雖然你不需要通過bind函數綁定this,但仍然要使用bind函數來綁定其他參數。

關於事件響應函數,還有一個地方需要註意。不管你在響應函數中有沒有顯式的聲明事件參數Event,React都會把事件Event作為參數傳遞給響應函數,且參數Event的位置總是在其他自定義參數的後面。例如,在代碼6和代碼7中,handleClick的參數中雖然沒有聲明Event參數,但你依然可以通過arguments[1]獲取到事件Event對象。

總結一下,三種事件處理的方式,第一種有額外的性能損失;第二種需要手動綁定this,代碼量增多;第三種用到了ES7的特性,目前並非默認支持,需要Babel插件的支持,但是寫法最為簡潔,也不需要手動綁定this。一般推薦使用第二種和第三種方式。


新書推薦《React進階之路》

作者:徐超

畢業於浙江大學,碩士,資深前端工程師,長期就職於能源物聯網公司遠景智能。8年軟件開發經驗,熟悉大前端技術,擁有豐富的Web前端和移動端開發經驗,尤其對React技術棧和移動Hybrid開發技術有深入的理解和實踐經驗。

技術分享圖片


技術分享圖片


美團點評廣告平臺大前端團隊招收2019\2020年前端實習生(偏動效方向)

有意者郵件:[email protected]

React 深入系列5:事件處理