原文地址: http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
正文
我们团队是 React框架 的忠实粉丝,并且已经尝试使用 ES6 来进行 React 开发。很高兴看到从 React 0.13 开始,官方推荐使用 ES6 类语法来进行组件的定义。
将 React 组件从 React 0.12 通过 createClass
定义的组件重构为基于 0.13 版本使用 ES6 类定义的组件只需要以下简单的几步。在后文中将一步一步的给予说明。
第一步 - 从组件构造函数中取出 propTypes
和 getDefaultProps
放到组件属性上
createClass
这个 API 期望的参数为一个字面量对象,同时可以定义方法和属性。而 ES6 中的类定义只允许定义方法,而不允许定义属性(The committee's rationale for this was primarily to have a minimal starting point for classes which could be easily agreed upon and expanded in ES7)。因为这个原因对于 propTypes
这样的属性,我们必须将其定义在 类定义 之外(we must define them outside of the class definition)。
另外的一个变化之处是在 React 0.13 版本中, props
属性被定义为 immutable data(不可变数据),所以 getDefaultProps
作为一个函数将不能在继续使用,因此也需要将其重构到构造函数之外。
Before:
var ExampleComponent = React.createClass({
propTypes: {
aStringProps: React.PropTypes.string
},
getDefaultProps: function() {
return { aStringProps: '' }
}
})
After:
var ExampleComponent = React.createClass({...});
ExampleComponent.propTypes = {
aStringProps: React.PropTypes.string
};
ExampleComponent.defaultProps = {
aStringProps: ''
}
第二步 - 从 createClass
到使用 ES6 Class 语法定义组件
ES6 Class 语法比起传统的对象字面量更为简洁。对于方法的定义不需要再使用 function
关键字,也不需要 ,
来分开多个方法。
Before:
var ExampleComponent = React.createClass({
render: function() {
return Hello World!
},
_handleClick: function() {
console.log(this);
}
})
After:
class ExampleComponent extends React.component {
render() {
return Hello World!
}
_handleClick() {
console.log(this)
}
}
第三步 - 绑定实例方法和回调到实例上
使用 createClass
时有一个便捷之处是默认的就将提供的方法绑定到了组件实例上。比如前面例子中的 _handleClick
方法里的 this
将会指向当前这个组件实例。当使用 ES6 Class 语法的时候,我们必须要手动为这些方法绑定执行上下文。React 团队推荐将绑定工作放在构造函数内完成(This is a stopgap until ES7 allows property initializers)
Before:
class ExampleComponent extends React.Component {
render() {
return Hello World!
}
_handleClick() {
console.log(this); // this is undefined
}
}
After:
class ExampleComponent extends React.Component {
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
}
render() {
return Hello World!
}
_handleClick() {
console.log(this); // this is an ExampleComponent
}
}
在文章结尾将会介绍我们对 Component 类的扩展,该类将会更好的实现自动绑定的过程。
第四步 - 将初始 state 定义移动到构造函数中
React 团队推荐使用一种更符合语义的方法来定义初始 state(在构造函数中将初始值存放在对应属性上)。也就是可以在构造函数中将 getInitialState
方法中的返回值赋值到 该实例对象的 this.state
属性上。
Before:
class ExampleComponent extends React.Component {
getInitialState() {
return Store.getState();
}
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
}
// ...
}
After:
class ExampleComponent extends React.Component {
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
this.state = Store.getState();
}
// ...
}
Conclusion
使用上面提到的少许的几步将一个存在的 React 组件转化为 ES6 Class 语法定义的组件是很简单的。同时直到 JavaScript 语法上有 mixins
特性之前使用 React.createClass
也是不被反对的。
Bonus Step - 更简洁的 this 绑定方法:
Before:
class ExampleComponent extends React.Component {
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
this._handleFoo = this._handleFoo.bind(this);
}
// ...
}
After:
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
}
class ExampleComponent extends BaseComponent {
constructor() {
super();
this._bind('_handleClick', '_handleFoo');
}
// ...
}
我们通过 _bind
方法将重复的绑定方法提取到 BaseComponent
类上使绑定的方法更为简洁。_bind
方法使用了很多 ES6 的特性: methods
参数使用了 rest parameter, forEach
里面使用了 箭头函数(arrow function).