原文:React Binding Patterns: 5 Approaches for Handling 'this'
说明:因个人水平有限,如有误译,欢迎指正。
JavaScript this 关键字的行为已经让很多开发者困惑不已了。
在 React 中至少有五种方式来处理 this 上下文。现在让我们看一下每一种方式的优劣。
1. 使用 React.createClass
如果您使用的是 React.createClass,React 会将所有的函数绑定到 this 上。因此 this 关键字会自动地绑定到您的组件实例上:
// 使用 React.createClass 很神奇,因为 `this` 已自动为您绑定。
onChange={this.handleChange}
然而,随着 ES6 中类的到来,这种创建类的非标准方式并不是 React 的未来。事实上,createClass 很可能在 React 的未来发行版本中被提取出去。
2. 在 Render 中绑定
接下来的这些方式都假设您是通过 ES6 类的方式来声明 React 组件的。如果您使用了一个 ES6 类,React 将不再为您自动绑定。其中一种方式就是在 render 中调用 bind:
onChange={this.handleChange.bind(this)}
这种方式很简洁,然而,由于函数在每一次渲染的时候都会重新分配,这就带来了性能上的影响。这听起来很重要,但是这种方式所带来的性能影响在大多数应用中并不明显。所以一开始因为性能的原因而将其排除出去是一个不成熟的优化。这里有一个例子会告诉您在什么时候这种方式会带来性能上的影响。
归根结底,如果您有了性能问题,避免在 render 中使用 bind 或箭头函数。
3. 在 Render 中使用箭头函数
这个方式与 #2 类似。您可以在 render 中使用箭头函数从而避免 this 上下文的更改:
onChange={e => this.handleChange(e)}
此方式与 #2 一样,具有潜在的性能影响。
下面可选的方式是值得考虑使用的,因为它们提供了更好的性能,却只需要您额外付出很少的代价。
4. 在构造器中绑定
避免在 render 中绑定的一种方式就是在构造器中绑定(另一种方式是下面讨论的 #5)。
这种方式 目前是 React 文档中推荐的。这也是我在 Pluralsight 视频 “Building Applications with React and Redux in ES6” 中所使用的方式。
然而,在大多数应用中,#2 和 #3 方式的性能影响并不显著,所以 #2 和 #3 方式的可读性和可维护性在多数应用中可能会高于性能方面的考虑。
但是假如您想要使用 stage-2 特性,下面一个选项可能是您最佳的选择。
5. 在类属性中使用箭头函数
这个技术依赖于 提议的类属性特性。想要使用此方式,您必须启用 transform-class-properties 或 stage-2 in Babel。
handleChange = () => {
// 在 render 中调用此函数
// 并且 this.whatever 在这里正常执行
};
这个方式具有多个优点:
- 箭头函数 在包裹的作用域中采用 this 绑定(换句话说,它们没有改变 this 的含义),所以这些事都会自动完成。
- 它避免了方案 #2 和 #3 中的性能问题。
- 它避免了方案 #4 中的重复劳动。
- 通过将相关的函数转化为箭头函数。它直接将 ES5 createClass 风格重构为现在的风格。事实上,使用 codemod 可以 完全自动化地处理 this。
总结
下面这个流程图对此做了总结。
下面是以上五种方式的示例代码:
// 方案 1:使用 React.createClass
var HelloWorld = React.createClass({
getInitialState() {
return { message: 'Hi' };
},
logMessage() {
// this magically works because React.createClass autobinds.
console.log(this.state.message);
},
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
});
// 方案 2:在 Render 中绑定
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hi' };
}
logMessage() {
// This works because of the bind in render below.
console.log(this.state.message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage.bind(this)} />
);
}
}
// 方案 3:在 Render 中使用箭头函数
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hi' };
}
logMessage() {
// This works because of the arrow function in render below.
console.log(this.state.message);
}
render() {
return (
<input type="button" value="Log" onClick={() => this.logMessage()} />
);
}
}
// 方案 4:在构造器中使用绑定
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = { message: 'Hi' };
this.logMessage = this.logMessage.bind(this);
}
logMessage() {
// This works because of the bind in the constructor above.
console.log(this.state.message);
}
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
}
// 方案 5:在类属性中使用箭头函数
class HelloWorld extends React.Component {
// Note that state is a property,
// so no constructor is needed in this case.
state = {
message: 'Hi'
};
logMessage = () => {
// This works because arrow funcs adopt the this binding of the enclosing scope.
console.log(this.state.message);
};
render() {
return (
<input type="button" value="Log" onClick={this.logMessage} />
);
}
}