為什麼在React Component需要bind繫結事件

破衣丁發表於2019-01-15
我們在 React class Component 繫結事件時,經常會通過 bind(this) 來繫結事件,比如:

class Foo extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(event){
    // todo something
  }
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}複製程式碼

下面就要看為什麼我們需要bind(this)在元件中。

JavaScript 中 this 繫結機制

預設繫結(Default Binding)

function display(){
 console.log(this); // this 指向全域性物件
}
display(); 複製程式碼

display( )在全域性的 window 作用域呼叫,所以函式內的 this 預設指向全域性的 window, 在 strict 模式 this 的值為undefined。

隱式繫結(Implicit binding)

var obj = {
 name: 'coco',
 display: function(){
   console.log(this.name); // this 指向 obj
  }
};
obj.display(); // coco複製程式碼

當我們通過obj呼叫 display( ) 時,this 上下文執行 obj, 但是當我們將 display( ) 賦給一個變數,比如:

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

display 被賦給 outerDisplay 這個變數,呼叫 outerDisplay( ) 時,相當於 Default Binding,this 上下文指向 global, 因此 this.name 找到的是全域性的 name。
很多時候,我們需要將函式作為引數通過callback方式來呼叫,也會使這個函式失去它的 this 上下文,比如:

function handleClick(callback) {
  callback()
}
var name = 'oh! global';
handleClick(obj.display);
// oh! global複製程式碼

當呼叫handleClick方法時,JavaScript重新將 obj.display 賦予 callback 這個引數,相當於 callback = obj.display ,display這個函式在handleClick作用域環境,就像Default Binding,裡面的 this 執行全域性物件。

顯式繫結(Explicit binding)

為了避免上面問題,我們可以通過 bind( ) 來顯式繫結 this 的值。

var name = "oh! global";
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay();
// coco複製程式碼

真正的原因在 JavaScript 不在 React

回到開始我們的問題:為什麼 React 元件事件繫結需要 bind 來繫結,如果我們不繫結,this 的值為 undefined。

class Foo {
  constructor(name){
    this.name = name
  }
  display(){
    console.log(this.name);
  }
}
var foo = new Foo('coco');
foo.display(); // coco

// 下面例子類似於在 React Component 中 handle 方法當作為回撥函式傳參
var display = foo.display;
display() // TypeError: this is undefined複製程式碼

我們在實際 React 元件例子中,假設 handleClick 方法沒有通過 bind 繫結,this 的值為 undefined, 它和上面例子類似handleClick 也是作為回撥函式傳參形式。
但是我們程式碼不是在 strict 模式下, 為什麼 this 的值不是全域性物件,就像前面的 default binding,而是undefined?
因為 class 類不管是原型方法還是靜態方法定義,“this”值在被呼叫的函式內部將為 undefined,具體原因見詳細
同樣,我們為了避免這個問題需要 bind 繫結:

class Foo {
  constructor(name){
    this.name = name
    this.display = this.display.bind(this);
  }
  display(){
    console.log(this.name);
  }
}
var foo = new Foo('coco');
foo.display(); // coco
var display = foo.display;
display(); // coco複製程式碼

當然,我們可以不在 constructor 中繫結 this, 比如:

var foo = new Foo('coco');
foo.display = foo.display.bind(foo);
var display = foo.display;
display(); // coco複製程式碼

但是,在 constructor 中繫結是最佳和最高效的地方,因為我們在初始化 class 時已經將函式繫結,讓 this 指向正確的上下文。

不用bind 繫結方式

當然,實際寫 React Component 還有其他的一些方式來使 this 指向這個 class :
最常用的 public class fields

class Foo extends React.Component{
  handleClick = () => {
    console.log(this); 
  }
 
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}複製程式碼
這是因為我們使用 public class fields 語法,handleClick 箭頭函式會自動將 this 繫結在 Foo 這個class, 具體就不做探究。

箭頭函式

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

這是因為在ES6中,箭頭函式 this 預設指向函式的宿主物件(或者函式所繫結的物件)。

其他

其他還有一些方法來使 this 指向 Foo 上下文,比如通過 ::繫結等,具體就不展開。

總結

React class 元件中,事件的 handle 方法其實就相當於回撥函式傳參方式賦值給了 callback,在執行 click 事件時
類似 element.addEventListener('click', callback, false ), handle 失去了隱式繫結的上下文,this 的值為 undefined。(為什麼是 undefined 而不是 global,上文有解釋)。
所以我們需要在 初始化呼叫 constructor 就通過 bind() 繫結 this, 當然我們不用 bind( )方式來繫結也可以有其他一些方法來時 this 指向正確的上下文。


相關文章