1. 程式人生 > >如何優雅地在React中處理事件響應

如何優雅地在React中處理事件響應

React中定義一個元件,可以通過React.createClass或者ES6的class。本文討論的React元件是基於class定義的元件。採用class的方式,程式碼結構更加清晰,可讀性強,而且React官方也推薦使用這種方式定義元件。

處理事件響應是Web應用中非常重要的一部分。React中,處理事件響應的方式有多種。

1.使用箭頭函式

先上程式碼:

//程式碼1
class MyComponent extends React.Component {

  render() {
    return (
      <button onClick={()=>{console.log('button clicked'
);}}> Click </button> ); } }

當事件響應邏輯比較複雜時,再把所有的邏輯直接寫在onClick的大括號內,就會導致render函式變得臃腫,不容易直觀地看出元件render出的元素結構。這時,可以把邏輯封裝成元件的一個方法,然後在箭頭函式中呼叫這個方法。如下所示:

//程式碼2
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> ); } }

這種方式最大的問題是,每次render呼叫時,都會重新建立一個事件的回撥函式,帶來額外的效能開銷,當元件的層級越低時,這種開銷就越大,因為任何一個上層元件的變化都可能會觸發這個元件的render方法。當然,在大多數情況下,這點效能損失是可以不必在意的。這種方式也有一個好處,就是不需要考慮this的指向問題,因為這種寫法保證箭頭函式中的this指向的總是當前元件。

2.使用元件方法

程式碼先:

//程式碼3
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {number: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({
      number: ++this.state.number
    });
  }

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

這種方式的好處是每次render,不會重新建立一個回撥函式,沒有額外的效能損失。需要注意的是,使用這種方式要在建構函式中為事件回撥函式繫結this: this.handleClick = this.handleClick.bind(this),否則handleClick中的this是undefined。這是因為ES6 語法的緣故,ES6 的 Class 構造出來的物件上的方法預設不繫結到 this 上,需要我們手動繫結。每次都手動繫結this是不是有點蛋疼?好吧,讓我們來看下一種方式。

3.屬性初始化語法(property initializer syntax)

使用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外掛獲取這個特性支援。

4.回撥函式傳參問題

事件的回撥函式預設是會被傳入一個事件物件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>
    );
  }
}

使用第二種方式的話,可以把繫結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。推薦使用第二種和第三種方式。