从 React 绑定 this,看 JS 语言发展和框架设计

react

在 javascript 语言中,关于 this 这个关键字的行为一直以来困扰着一代又一代初级开发者。同时 this,也充分反应了 javascript 的诡异与灵活。

但是请别误会,这篇文章并不会对 this 的特征进行全方位讲解,因为这些内容都可以在各种前端书籍中找到答案。这里,我试图结合 React 事件处理函数关于 this 绑定的演化史,谈一谈这个框架设计以及 javascript 语言在这一细节上的进步和完善。同时对比 this 绑定的不同方案,让大家对 React 、ES next 有一个更清晰的认识。

React 处理 this 上下文环境已经有至少五年历史了。五年期间,方案辈出,我们先来总结一下。

方法一:React.createClass 自动绑定

React 中创建组件的方式已经很多,比较古老的诸如 React.createClass 应该很多人并不陌生。当然,从 React 0.13 开始,可以使用 ES6 Class 代替 React.createClass 了,这应该是今后推荐的方法。
但是需要知道,React.createClass 创建的组件,可以自动绑定 this。也就是说,this 这个关键字会自动绑定在组件实例上面。

// This magically works with React.createClass
// because `this` is bound for you.
onChange = {this.handleChange}

当然很遗憾,对于组件的创建,官方已经推荐使用 class 声明组件或使用 functional 无状态组件:

Later, classes were added to the language as part of ES2015, so we added the ability to create React components using JavaScript classes. Along with functional components, JavaScript classes are now the preferred way to create components in React.
For your existing createClass components, we recommend that you migrate them to JavaScript classes.

我认为,这其实是 React 框架本身的自我完善和对未来的迎合,是框架和语言发展的大势所趋。


方法二:渲染时绑定

通过前文,我们知道最传统的组件创建方式不会有 this 绑定的困扰。接下来,我们假定所有的组件都采取 ES6 classes 方式声明。这种情况下,this 无法自动绑定。一个常见的解决方案便是:

onChange = {this.handleChange.bind(this)}

这种方法简明扼要,但是有一个潜在的性能问题:当组件每次重新渲染时,都会有一个新的函数创建。OMG! 这听上去貌似是一个很大的问题,但是其实在真正的开发场景中,由此引发的性能问题往往不值一提(除非是大型组件消费类应用或游戏)。


方法三:箭头函数绑定

这种方法其实和第二种类似,拜 ES6 箭头函数所赐,我们可以隐式绑定 this:

onChange = {e => this.handleChange(e)}

当然,也与第二种方法一样,它同样存在潜在的性能问题
下面将要介绍的两种方法,可以有效规避不必要的性能消耗,请继续阅读。


方法四:Constructor 内绑定

constructor 方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。

所以我们可以:

constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
}

这种方式往往被推荐为“最佳实践”,也是笔者最为常用的方法。

但是就个人习惯而言,我认为与前两种方法相比,constructor 内绑定在可读性和可维护性上也许有些欠缺。
同时,我们知道在 constructor 声明的方法不会存在实例的原型上,而属于实例本身的方法。每个实例都有同样一个 handleChange,这本身也是一种重复和浪费。

如果你对 ES next 一直抱有开放的思想,且能够使用 stage-2 的特性,不妨尝试一下最后一种方案。


方法五:Class 属性中使用 = 和箭头函数

这个方法依赖于 ES next 的新特性,请参考 tc 39: Public Class Fields

handleChange = () => {
      // call this function from render 
      // and this.whatever in here works fine.
};

我们来总结一下这种方式的优点:

  • 使用箭头函数,有效绑定了 this;
  • 没有第二种方法和第三种方法的潜在性能问题;
  • 避免了方法四的组件实例重复问题;
  • 我们可以直接从 ES5 createClass 重构得来。


总结

本文在对比 React 绑定 this 的五种方法的同时,也由远及近了解了 javascript 语言的发展:从 ES5 的 bind, 到 ES6 的箭头函数,再到 ES next 对 class 的改进。

React 作为蓬勃发展的框架也同样在与时具进,不断完善,结合语言特性的发展不断调整着自身。

最后,我们通过这张图片来完整回顾:

各种方式逻辑

本文参考了 Cory House 的文章:5 Approaches for Handling this,并在此基础上进行延伸。



我的其他一些关于 React 文章:

Happy Coding!

PS: 作者Github仓库,欢迎通过代码各种形式交流。