[譯] 爲何須要在 React 類組件中爲事件處理程序綁定 this

背景圖源來自 Kaley Dykstra 併發布在 Unsplash 上,源代碼圖像生成自 carbon.now.sh前端

在使用 React 時,您不免遇到受控組件和事件處理程序。在自定義組件的構造函數中,咱們須要使用 .bind() 來將方法綁定到組件實例上面。react

class Foo extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(event){
    // 你的事件處理邏輯
  }

  render(){
    return (
      <button type="button" 
      onClick={this.handleClick}>
      Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
複製代碼

在這篇文章中,咱們將探究爲何要這麼作。android

若是你對 .bind() 尚不瞭解,推薦閱讀 這篇文章ios

應責怪 JavaScript,而不是 React

好吧,責怪聽起來有些苛刻。若是按照 React 和 JSX 的語法,咱們並不須要這麼作。其實綁定 this 是 JavaScript 中的語法。git

讓咱們看看,若是不將事件處理程序綁定到組件實例上,會發生什麼:github

class Foo extends React.Component{
  constructor( props ){
    super( props );
  }

  handleClick(event){
    console.log(this); // 'this' 值爲 undefined
  }

  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
複製代碼

若是你運行這個代碼,點擊 「Click Me」 按鈕,檢查你的控制檯,你將會看到控制檯打印出 undefined,這個值是 handleClick() 方法內部的 this 值。handleClick() 方法彷佛已經丟失了其上下文(組件實例),即 this 值。後端

在 JavaScript 中,this 的綁定是如何工做的

正如我上文提到的,是 JavaScript 的 this 綁定機制致使了上述狀況的發生。在這篇文章中,我不會深刻探討太多細節,可是 這篇文章 能夠幫助你進一步學習在 JavaScript 中 this 的綁定是如何工做的。bash

與咱們討論相關的是,函數內部的 this 的值取決於該函數如何被調用。babel

默認綁定

function display(){
 console.log(this); // 'this' 將指向全局變量
}

display(); 
複製代碼

這是一個普通的函數調用。在這種狀況下,display() 方法中的 this 在非嚴格模式下指向 window 或 global 對象。在嚴格模式下,this 指向 undefined併發

隱式綁定

var obj = {
 name: 'Saurabh',
 display: function(){
   console.log(this.name); // 'this' 指向 obj
  }
};

obj.display(); // Saurabh 
複製代碼

當咱們以一個 obj 對象來調用這個函數時,display() 方法內部的 this 指向 obj

可是,當咱們將這個函數引用賦值給某個其餘變量並使用這個新的函數引用去調用該函數時,咱們在 display() 中得到了不一樣的this值。

var name = "uh oh! global";
var outerDisplay = obj.display;
outerDisplay(); // uh oh! global
複製代碼

在上面的例子裏,當咱們調用 outerDisplay() 時,咱們沒有指定一個具體的上下文對象。這是一個沒有全部者對象的純函數調用。在這種狀況下,display() 內部的 this 值回退到默認綁定。如今這個 this 指向全局對象,在嚴格模式下,它指向 undefined

在將這些函數以回調的形式傳遞給另外一個自定義函數、第三方庫函數或者像 setTimeout 這樣的內置JavaScript函數時,上面提到的判斷方法會特別實用。

考慮下方的代碼,當自定義一個 setTimeout 方法並調用它,會發生什麼。

//setTimeout 的虛擬實現
function setTimeout(callback, delay){

   //等待 'delay' 數個毫秒

   callback();
}

setTimeout( obj.display, 1000 );
複製代碼

咱們能夠分析出,當調用 setTimeout 時,JavaScript 在內部將 obj.display 賦給參數 callback

callback = obj.display;
複製代碼

正如咱們以前分析的,這種賦值操做會致使 display() 函數丟失其上下文。當此函數最終在 setTimeout 函數裏面被調用時,display()內部的 this 的值會退回至默認綁定

var name = "uh oh! global";
setTimeout( obj.display, 1000 );

// uh oh! global
複製代碼

明確綁定

爲了不這種狀況,咱們可使用 明確綁定方法,將 this 的值經過 bind() 方法綁定到函數上。

var name = "uh oh! global";
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay();

// Saurabh
複製代碼

如今,當咱們調用 outerDisplay() 時,this 的值指向 display() 內部的 obj

即時咱們將 obj.display 直接做爲 callback 參數傳遞給函數,display() 內部的 this 也會正確地指向 obj

僅使用 JavaScript 從新建立場景

在本文的開頭,咱們建立了一個類名爲 Foo 的 React 組件。若是咱們不將 this 綁定到事件上,事件內的值會變成 undefined

正如我上文解釋的那樣,這是由 JavaScript 中 this 綁定的方式決定的,與React的工做方式無關。所以,讓咱們刪除 React 自己的代碼,並構建一個相似的純 JavaScript 示例,來模擬此行爲。

class Foo {
  constructor(name){
    this.name = name
  }

  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

//下面的賦值操做模擬了上下文的丟失。 
//與實際在 React Component 中將處理程序做爲 callback 參數傳遞類似。
var display = foo.display; 
display(); // TypeError: this is undefined
複製代碼

咱們不是模擬實際的事件和處理程序,而是用同義代碼替代。正如咱們在 React 組件示例中所看到的那樣,因爲將處理程序做爲回調傳遞後,丟失了上下文,致使 this 值變成 undefined。這也是咱們在這個純 JavaScript 代碼片斷中觀察到的。

你可能會問:「等一下!難道 this 的值不是應該指向全局對象麼,由於咱們是按照默認綁定的規則,在非嚴格模式下運行的它。「

答案是否認的 緣由以下:

類聲明類表達式的主體以 嚴格模式 執行,主要包括構造函數、靜態方法和原型方法。Getter 和 setter 函數也在嚴格模式下執行。

你能夠在 這裏 閱讀完整的文章。

因此爲了不錯誤,咱們須要像下文這樣綁定 this 的值:

class Foo {
  constructor(name){
    this.name = name
    this.display = this.display.bind(this);
  }

  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh
複製代碼

咱們不只能夠在構造函數中執行此操做,也能夠在其餘位置執行此操做。考慮這個:

class Foo {
  constructor(name){
    this.name = name;
  }

  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display = foo.display.bind(foo);
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh
複製代碼

但因爲構造函數是全部初始化發生的地方,所以它是編寫綁定事件語句最佳的位置。

爲何咱們不須要爲箭頭函數綁定 ‘this’

在 React 組件內,咱們有另外兩種定義事件處理程序的方式。

class Foo extends React.Component{
  handleClick = () => {
    console.log(this); 
  }
 
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
複製代碼
class Foo extends React.Component{
 handleClick(event){
    console.log(this);
  }
 
  render(){
    return (
      <button type="button" onClick={(e) => this.handleClick(e)}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
複製代碼

這兩個都使用了ES6引入的箭頭函數。當使用這些替代方法時,咱們的事件處理程序已經自動綁定到了組件實例上,而且咱們不須要在構造函數中綁定它。

緣由是在箭頭函數的狀況下,this 是有詞法約束力的。這意味它可使用封閉的函數上下文或者全局上下文做爲 this 的值。

在公共類字段語法的例子中,箭頭函數被包含在 Foo 類中或者構造函數中,因此它的上下文就是組件實例,而這就是咱們想要的。

在箭頭函數做爲回調的例子中,箭頭函數被包含在 render() 方法中,該方法由 React 在組件實例的上下文中調用。這就是爲何箭頭函數也能夠捕獲相同的上下文,而且其中的 this 值將正確的指向組件實例。

有關 this 綁定的更多細節,請查看 此優秀資源

總結

在 React 的類組件中,當咱們把事件處理函數引用做爲回調傳遞過去,以下所示:

<button type="button" onClick={this.handleClick}>Click Me</button>

複製代碼

事件處理程序方法會丟失其隱式綁定的上下文。當事件被觸發而且處理程序被調用時,this的值會回退到默認綁定,即值爲 undefined,這是由於類聲明和原型方法是以嚴格模式運行。

當咱們將事件處理程序的 this 綁定到構造函數中的組件實例時,咱們能夠將它做爲回調傳遞,而不用擔憂會丟失它的上下文。

箭頭函數能夠免除這種行爲,由於它使用的是詞法 this 綁定,會將其自動綁定到定義他們的函數上下文。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索