Fork me on github

【React思考录】为什么React Class Component需要bind绑定事件?

最近在使用React+Typescript重构一个应用,后面看到同事在写react组件的方法时,是采用箭头函数的写法。这让我想起在 React Class Component 绑定事件时,经常会通过 bind(this) 来绑定事件,比如:

class Fn extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }
  // 1. 普通函数的写法
  handleClick(event){
    // todo something
  }
  // 2. 箭头函数的写法
  handleArrowClick = (event) => {
    
  }
  render(){
    return (
      <div>
        <button type="button" onClick={this.handleClick}>
          Click Me
        </button>
        <button type="button" onClick={this.handleArrowClick}>
          Click Me
        </button>
      </div>
    );
  }
}

为什么我们需要bind(this)在组件中?为什么可以用箭头函数代替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 Class Component 还有其他的一些方式来使 this 指向这个 class , 那么为什么需要别的方式,不使用bind绑定方式呢?

  • 这种写法难看不说,还会对 React 组件的 shouldComponentUpdate 优化造成影响。

  • 这是因为 React 提供了 shouldComponentUpdate 让开发者能够控制避免不必要的 render,还提供了在 shouldComponentUpdate 自动进行 Shallow Compare 的 React.PureComponent, 继承自 PureComponent 的组件只要 props 和 state 中的值不变,组件就不会重新 render。

  • 然而如果用了 bind this,每次父组件渲染,传给子组件的 props.onClick 都会变,PureComponent 的 Shallow Compare 基本上就失效了,除非你手动实现 shouldComponentUpdate.

最常用的 public class fields

这是因为我们使用 public class fields 语法,handleClick 箭头函数会自动将 this 绑定在 Foo 这个class, 具体就不做探究。

箭头函数

class Fn extends React.Component{
  handleClick = (event) => {
    console.log(this); 
  }
  
  // 相当于
  constructor(props){
    super(props)
		this.handleClick = (event) => {
    	console.log(this); 
  	}
  }
  render(){
    return (
      <button type="button" onClick={(e) => this.handleClick(e)}>
        Click Me
      </button>

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

这是因为在ES6中,箭头函数 this 默认指向函数的宿主对象(或者函数所绑定的对象)。

其他

还有一些方法来使 this 指向 Foo 上下文,比如通过 ::绑定等,具体就不展开,可以自己去查 React 绑定 this 的几种方式。

总结

  • React class 组件中,事件的 handle 方法其实就相当于回调函数传参方式赋值给了 callback,在执行 click 事件时 类似 element.addEventListener('click', callback, false ), handle 失去了隐式绑定的上下文,this 的值为 undefined。(为什么是 undefined 而不是 global,上文有解释)。

  • 所以我们需要在 初始化调用 constructor 就通过 bind() 绑定 this, 当然我们不用 bind( )方式来绑定也可以有其他一些方法来时 this 指向正确的上下文。

参考:

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

函数作为React组件的方法时, 箭头函数和普通函数的区别是什么?

posted @ 2020-11-04 19:27  Zenquan  阅读(554)  评论(0编辑  收藏  举报