React中class组件bind及性能比较

3,905 阅读3分钟

最近小伙伴问了我关于react中class中事件为什么要进行绑定,感觉他对这块的理解比较模糊,特此总结一下。

为什么要进行bind

首先,我们来看一个例子

let test = {
    a: 1,
    b: function() {
        console.log(this.a)
    }
}

我们执行如下操作:

test.b() // 1
const {b} = test;
b() // undefined

首先我们使用test来调用其中的方法b,此时根据谁调用this指向谁的原则,this.a输出结果就是test中的a,即为1 当我们使用es6的解构,从test中将b解构出来,即将test.b赋值给一个变量b,当我们执行b()时,依据谁调用指向谁的原则,b指向了window,此时window下无a,因此输出undefined。

如果我们设置严格模式:

let test = {
    a: 1,
    b: function() {
         "use strict"
        console.log(this.a)
    }
}
const {b} = test;
b()

此时运行b()时,b中的this指向undefined。 那么我们来看看react中:

render() {
    return (
        <div>
            <a onClick={this.clickEvent}>test</a>
        </div>
    )
}

如代码所示,我们给a标签绑定了一个onClick事件。我们大家都知道,react是从虚拟DOM映射成为真实DOM,onClick也不是真实DOM中的click事件,只是在从虚拟DOM生成真实DOM的过程中,将这个this.clickEvent赋值到真实DOM上的click事件。因此,render中的onClick相当于一个中间量的存在,因为中间量的存在,当真实DOM调用this.clickEvent时,此时的this自然无法指向到我们定义的组件实例,导致this指向丢失,因此必须进行bind。

bind的方法有哪些

class Test {
    consoleName () {
      this.con('Hello Test');
    }
    con(name) {
      console.log(name);
    }
}
const test = new Test();
test.consoleName(); // 'Hello Test'
const { consoleName } = test;
consoleName(); // Cannot read property 'con' of undefined

如上代码所示,我们从test中解构出方法并赋值给consoleName,此时执行方法的时候,this指向丢失,导致consoleName中无法找到con。此时this是什么呢?ES6的class内部默认严格模式,严格模式下,如果 this 没有被执行环境定义(即没有作为对象的属性或者方法调用),那它将保持为进入执行环境时的值,即undefined。那怎么才能不报错呢?

方法1 constructor bind

class Test {
    constructor() {
        this.consoleName = this.consoleName.bind(this);
        this.con = this.con.bind(this);
    }
    consoleName () {
      this.con('Hello Test');
    }
    con(name) {
      console.log(name);
    }
}
const test = new Test();
test.consoleName(); // 'Hello Test'
const { consoleName } = test;
consoleName(); //  'Hello Test'

方法2 箭头函数

class Test {
    consoleName = () => {
      this.con('Hello Test');
    }
    con = (name) => {
      console.log(name);
    }
}
const test = new Test();
test.consoleName(); // 'Hello Test'
const { consoleName } = test;
consoleName(); // 'Hello Test'

方法3 render中bind

在render中直接bind。

因此总结下来如下:

render () {
    return (
        <a onClick={this.clickEvent}>方法1 constructor绑定</a>
        <a onClick={() => this.clickEvent()}>方法2 箭头函数绑定</a>
        <a onClick={this.clickEvent.bind(this)}>方法3 直接绑定</a>
    )
}

性能对比

首先,官方推荐第一种方法绑定this。那么接下来我们分析一下:

第一种方法,只在构造函数里面渲染一次,即实例化时执行一遍。

第二种方法,每一次render的时候,都会生成一个新的箭头函数。

第三种方法,每一次render的时候都会执行一次函数。

我们先说下PureComponent,PureComponent是Component的优化,可以少写 shouldComponentUpdate 函数,从而达到组件的 props 和state 都没发生改变,render就不渲染的效果。假如说你使用了第二种和第三种方法,那么在判断是否需要render的时候,PureComponent的shallowCompare会认为你的onClick与之前的不相等,自然就需要重新render。这一定程度上带来了不少的性能损耗。并且,箭头函数的方式,我们可以在chrome dev tools堆栈中看到一堆的anonymous,带来调试上的困难。

因此方法1性能最好,也是官方推荐。方法2和方法3会带来重新渲染的问题。