• 【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组件的方法时, 箭头函数和普通函数的区别是什么?

  • 相关阅读:
    泛型
    HTTP和HTTPS
    计算机网络(三)应用层
    练习38-操作列表
    第27讲:集合—在我的世界里,你就是唯一
    第25~26讲:字典:当索引不好用时
    第1~2讲:数据结构和算法绪论
    第23~24讲:这帮小兔崽子(斐波那契数列)和汉诺塔游戏
    练习36--设计和调试
    001-定义电子日历类
  • 原文地址:https://www.cnblogs.com/Jomsou/p/13927659.html
Copyright © 2020-2023  润新知