为什么在React Component需要bind绑定事件

我们在 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 指向正确的上下文。


猜你喜欢

转载自juejin.im/post/5c3deda4518825253208fc0e