1. 程式人生 > >React躬行記(6)——事件

React躬行記(6)——事件

  React在原生事件的基礎上,重新設計了一套跨瀏覽器的合成事件(SyntheticEvent),在事件傳播、註冊方式、事件物件等多個方面都做了特別的處理。

一、註冊事件

  合成事件採用宣告式的註冊方式,類似於設定HTML屬性,但有兩點不同:

(1)事件要採用小駝峰的命名法,而不是全部小寫,例如onclick要寫成onClick。

(2)在JSX中的事件處理程式是一個函式引用,而不是一段字串,如下程式碼所示,其中handle()是一個函式。

<button onclick="handle()">提交</button>
<button onClick={handle}>提交</button>

  兩個<button>元素都註冊了點選事件,其中前者採用了傳統的事件註冊,後者使用了合成事件,通過對比可發現它們之間有兩處明顯的不同。

  React的事件型別是原生事件型別的一個子集,在上面的示例中,為按鈕添加了Mouse型別的onClick事件。除了常規的Focus、Form、Touch等型別之外,React還支援Selection、Wheel、Media等有特殊功能的事件型別。

二、事件傳播

  React中的事件最終都會轉換成原生事件,但不會註冊到某個真實的DOM元素,而是繫結到根元素,使用事件委託技術進行事件代理,再由一個總的事件監聽器管理所有的事件處理程式。

  React中的事件傳播只有冒泡這一種形式,而冒泡會先在DOM樹中向上傳播,一直到根元素,然後再從JSX中觸發的React元素開始向它的父元素傳播,依次執行繫結的事件處理程式。在下面的示例中,分別為頁面中的根元素和<body>元素,以及元件中的<div>元素和<button>元素綁定了點選事件,其中<div>是<button>的父元素。

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>
    );
  }
}

  當點選提交按鈕時,在控制檯輸出的順序是“body”、“html”、“子元素”、“父元素”。在React的合成事件中,也會包含一個事件物件(例如handle1()函式中的e引數)。如果要停止冒泡傳播,那麼可以在相應的事件處理程式中呼叫e.stopPropagation()。

三、事件物件

  React基於W3C規範對事件物件進行了一層封裝,不僅保證了瀏覽器的相容性,還提供了一個訪問原生事件物件的入口(即呼叫自身的nativeEvent屬性)。注意,在合成事件中阻止元素的預設行為,不能用返回false的方式,得呼叫事件物件的preventDefault()方法。

  合成事件是被池化的,在執行完事件處理程式後,其引數(即事件物件)的屬性將會被丟棄,例如通過定時器非同步呼叫事件物件的type屬性,如下所示。

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>;
  }
}

  點選提交按鈕,在控制檯首先會輸出“click”,然後再輸出null。如果想要保持事件物件的屬性,那麼可以呼叫它的persist()方法。繼續上一個例子,只要在handle()方法中加一句e.persist(),如下程式碼所示,就能在定時器中輸出“click”。

handle(e) {
  console.log(e.type);           //"click"
  setTimeout(function() {
    console.log(e.type);         //"click"
  }, 0);
  e.persist();
}

四、this

  事件處理程式中的this不會自動繫結,即預設是沒有指向的。在下面的例子中,點選提交按鈕,會先觸發註冊的handle()方法,然後在方法內輸出的this的值是undefined。

class Btn extends React.Component {
  handle() {
    console.log(this);           //undefined
  }
  render() {
    return <button onClick={this.handle}>提交</button>;
  }
}

  有兩種方式可以解決this指向的問題,分別是箭頭函式和bind()方法。

1)箭頭函式

  將事件處理程式改成箭頭函式的形式,就能糾正this的指向,如下程式碼所示。

class Btn extends React.Component {
  handle() {
    console.log(this);         //Btn例項
  }
  render() {
    return <button onClick={() => this.handle()}>提交</button>;
  }
}

  箭頭函式是不包含this的,在其內部使用的this是從上一層的作用域中繼承而來的,其指向的是函式宣告時所在的物件,即當前元件的例項,注意,像上面的<button>元素那樣,直接在其屬性中使用箭頭函式,會讓元件在每次渲染時,建立一個不同的回撥函式。

  可以換一種寫法,避免每次新建函式,如下程式碼所示,將箭頭函式轉移到方法宣告的時候。

class Btn extends React.Component {
  handle = () => {
    console.log(this);         //Btn例項
  }
  render() {
    return <button onClick={this.handle}>提交</button>;
  }
}

2)bind()

  為事件處理程式呼叫bind()方法,同樣也能糾正this的指向,如下程式碼所示。

class Btn extends React.Component {
  handle() {
    console.log(this);         //Btn例項
  }
  render() {
    return <button onClick={this.handle.bind(this)}>提交</button>;
  }
}

  函式的bind()方法能建立一個繫結指定物件的函式,在上面的render()方法中,傳遞給bind()方法的this,其指向的正是當前元件的例項。除了在元素的屬性中呼叫bind()方法之外,還可以將其移到元件的建構函式中,如下所示。

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>;
  }
}

五、事件處理程式的引數

  事件處理程式預設會接收一個事件物件,如果要向其傳遞額外的引數,那麼可以通過兩種方式實現。

  第一種是用箭頭函式,如下程式碼所示,顯式地將所有引數傳遞給事件處理程式。

class Btn extends React.Component {
  handle(e, name) {
    console.log(e, name);
  }
  render() {
    return <button onClick={(e) => this.handle(e, "strick")}>提交</button>;
  }
}

  第二種是用bind()方法,如下程式碼所示,事件物件會被隱式的傳遞過去,並且必須位於事件處理程式引數列表的最後。

class Btn extends React.Component {
  handle(name, e) {
    console.log(name, e);
  }
  render() {
    return <button onClick={this.handle.bind(this, "strick")}>bind</button>;
  }
}

&n