[译]React 与 ES6 - 第三部分,为 React 类绑定方法(ES7 同理)

React 与 ES6 - 第三部分,为 React 类绑定方法(ES7 同理)
=

这篇文章,是我们探索在 ECMAScript6 和 ECMAScript7 中 React 的使用方法的系列文章的第三篇。

想看这个系列的其他文章,请点如下链接:

React JavaScript
React ECMAScript

这个文章中的相关代码片段,也可以在 GitHub 中找到

最后更新日期: 2016年6月18日,更新内容包含了 React15 以及 Babel6。

这个系列的旧文章里,有一篇讲到了“CartItem 渲染方法”,如果你看过的话,可能会对 {this.increaseQty.bind(this)} 这种写法有点疑惑。

如果我们在 ES6 的代码里,对同样的 demo 用 {this.increaseQty} 来绑定一个组件的事件处理函数,浏览器会报 Uncaught TypeError: Cannot read property 'setState' of undefined 错误:

在 ES6 中用老的写法,会报 Uncahught TypeError

这是因为在 ES6 中,函数的 this 绑定规则已经发生了变化,我们在调用 this 的时候,调用的并不是类本身,而是 undefined。但是如果你在写 React 的时候用的是 React.createClass() 这种方法, React 会自动把所有类的方法的 this 绑定到对应的实例上。

在 React 组件开始支持用 ES6 class 来实现的时候,React 小组决定不再支持自动绑定。详细的原因,可以看这篇文章

下面来看看在用 ES6 class 写 JSX 文件的时候,怎么给类的方法绑定 this 值。

1. 用 Function.prototype.bind()

如下:

1
2
3
4
5
export default class CartItem extends React.Component {
render() {
<button onClick={this.increaseQty.bind(this)} className="button success">+</button>
}
}

由于 ES6 中类的方法本质上是 JavaScript 函数,因此继承了来自于 Function 原型上的 bind() 方法。现在,再调用 JSX 里的 increaseQty() 方法的时候,this 就会指向类的实例。如果对 Function.prototype.bind() 有疑惑,可以看这篇 MDN 文章

2. 在构造函数中进行绑定

1
2
3
4
5
6
7
8
9
10
11
export default class CartItem extends React.Component {

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

render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}

这样就不需要在 JSX 里用 bind() 方法了,但是增加了构造函数里的代码。

3. 用箭头函数以及构造函数

ES6 的箭头函数 被调用的时候,this 是函数执行的上下文。我们可以利用这个特性,在构造函数里重新定义 increaseQty()

1
2
3
4
5
6
7
8
9
10
11
export default class CartItem extends React.Component {

constructor(props) {
super(props);
this._increaseQty = () => this.increaseQty();
}

render() {
<button onClick={_this.increaseQty} className="button success">+</button>
}
}

(译注:是不是写错了)

4. 用箭头函数以及 ES2015+ 的类属性

除了上面提到的 3 种方法,还可以把箭头函数跟 ES2015+ 的类属性组合起来写:

1
2
3
4
5
6
7
8
export default class CartItem extends React.Component {

increaseQty = () => this.increaseQty();

render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}

哈哈,这次我们没有用长的构造函数代码来实现我们的需求了,而是巧妙的利用了类的属性初始化。

警告: 类属性现在还不是当前的 JavaScript 标准,但是可以用 Babel 的实验版本标记(也就是 stage 0)来解决这个问题。关于 Babel 的使用方法,可以查看 Babel 文档
这个系列的文章 React and ES6 - Part 2, React Classes and ES7 Property Initializers 里就已经在用 stage 0 了,所以在这篇文章里,应该不是什么问题。

5. 用 ES2015+ 的函数绑定语法

最近 Babel 增加了一个语法糖,用 :: 来表示 Function.prototype.bind(),这个内容的细节不再展开。当然,如果你想了解细节,有些人已经在 Babel 官方文章 里对这个作了很好的解释。

下面是用了 ES2015+ 绑定语法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
export default class CartItem extends React.Component {

constructor(props) {
super(props);
this.increaseQty = ::this.increaseQty;
// line above is an equivalent to this.increaseQty = this.increaseQty.bind(this);
}

render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}

友情提示,这是一个实验特性,如果想用的话,先考虑一下风险问题。

6. 直接在 JSX 里用 ES2015+ 的函数绑定语法

直接在 JSX 里用 ES2015+ 的语法糖,就不用再写构造函数的代码了:

1
2
3
4
5
export default class CartItem extends React.Component {
render() {
<button onClick={::this.increaseQty} className="button success">+</button>
}
}

看看,代码写起来很简洁,但是,这样会导致每次子组件被渲染的时候,重新初始化一个函数(译注:有关这个问题的扩展阅读请看这里),所以性能上是存在问题的。如果你想用纯渲染函数(或者 ES2016 的类)的时候,这样写会导致更严重的问题。

结论

这篇文章里我们写了若干种给 React 组件的类的方法绑定 this 值的方式。这些代码我已经在第二部分的基础上写了一些测试用例

在下篇文章中,我们要讲的是用 ES2015 写 React 的时候,有关 state 的问题。