[译]React on ES6+

原文地址: http://babeljs.io/blog/2015/06/07/react-on-es6-plus

这是一篇来自Steven Luscher的特邀文章,Steven在Facebook从事于Relay的开发,一个javascript框架使用React与GraphQL用来构建应用。在InstagramGitHubTwitter关注Steven。

当我们今年重构Instagram Web时,更加热衷于使用一些ES6+的新特性来构建React组件。请允许我强调一些新特性来改变你构建React应用的方法,让它变得更加简单与有趣。

Classes (类)

当我们选择使用ES6+的类语法定义来构建React组件时,这便是到目前为止代码层最明显的改变。我们能定义一个真正的ES6类继承自React.Component,来替代React.createClass去定义一个组件:

1
2
3
4
5
class Photo extends React.Component {
render() {
return <img alt={this.props.caption} src={this.props.src} />;
}
}

你会马上意识到一个细微的不同 - 一个更为精炼的语法当你定义类时可以被使用:

1
2
3
4
5
6
7
8
9
10
// ES5
var Photo = React.createClass({
handleDoubleTap: function(e) { … },
render: function() { … },
});
// ES6+
class Photo extends React.Component {
handleDoubleTap(e) { … }
render() { … }
}

我们可以略去一对括号,一个结尾分号,对于每个方法而言可以略去一个冒号,一个function关键字和一个逗号。(这段话好傻逼)

当你使用新的类语法时,你可以定义任何生命周期方法除了componentWillMount,类的constructor现在已经充当了componentWillMount的角色:

1
2
3
4
5
6
7
8
9
10
11
// ES5
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
// ES6+
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// 在componentWillMount中执行的操作可以放在这里
}
}

Property initializers (属性初始化器)

在ES6+的类中,属性类型与默认值作为类自身的静态属性。当然,使用ES7的属性初始化器,也作为组件的初始化状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ES5
var Video = React.createClass({
getDefaultProps: function() {
return {
autoPlay: false,
maxLoops: 10,
};
},
getInitialState: function() {
return {
loopsRemaining: this.props.maxLoops,
};
},
propTypes: {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
},
});
// ES6+
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
}
state = {
loopsRemaining: this.props.maxLoops,
}
}

ES7属性初始化器是在类的构造函数中执行的,this指向类在构造函数下的实例,所以初始化状态设置仍依靠this.props。由此,我们不必定义属性的默认值与在getter方法中初始化状态。

Arrow functions (箭头函数)

React.createClass方法进行了一些额外的绑定工作在你的组件实例方法中,去确保在这些方法中this关键字指向组件的实例。

1
2
3
4
5
6
7
// React.createClass已经自动绑定
var PostInfo = React.createClass({
handleOptionsButtonClick: function(e) {
// 这里,'this'指向组件实例。
this.setState({showOptionsModal: true});
},
});

当我们定义组件使用ES6类语法后,我们不再需要React.createClass方法,这使我们需要手工绑定实例方法当我们有如下行为:

1
2
3
4
5
6
7
8
9
10
11
12
// 在哪里需要时,进行手工绑定
class PostInfo extends React.Component {
constructor(props) {
super(props);
// 手动绑定this到该实例方法
this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
}
handleOptionsButtonClick(e) {
// 确认this已经指向了该组件实例
this.setState({showOptionsModal: true});
}
}

幸运的是,结合两个ES6+的特性 - 箭头函数和属性初始化器,绑定组件实例变得十分轻而易举。

1
2
3
4
5
class PostInfo extends React.Component {
handleOptionsButtonClick = (e) => {
this.setState({showOptionsModal: true});
}
}

ES6箭头函数内部围绕相同词法的this展开代码,这个方法使ES7属性构造器在作用域内,由此给了我们所期望的结果。看看转换代码去了解它是怎么工作的。

Dynamic property names & template strings (动态属性名称 & 模板字符串)

对象常量增强有一条包含分配派生属性名称的能力。我们可能最初会这样地去设置一些状态:

1
2
3
4
5
6
7
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}

现在我们能够去构建一个javascript表达式在运行时确定对象的属性名称。这里,我们使用一个模板字符串去确定哪一个属性要被设置状态:

1
2
3
4
5
6
7
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}

Destructuring & spread attributes (解构 & 属性)

当我们编写组件时,我们可能需要把大部分父组件值传递给子组件,而不是全部。在结合ES6+解构与JSX的延展属性,将可能很轻松地实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AutoloadingPostsGrid extends React.Component {
render() {
var {
className,
...others, // 包含this.props除了className外的所有属性
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}

我们也可以结合JSX的常规属性与延展属性,利用简单的优先原则去实现覆盖与默认。这个div将得到className“override”即使在this.props中存在属性名称className:

1
2
3
<div {...this.props} className="override">
</div>

这个div按常理来讲应该拥有className“base”,除非在this.props中存在属性名称className去覆盖它:

1
2
3
<div className="base" {...this.props}>
</div>

Thanks for reading (感谢阅读)

我希望你能像我们一样享受ES6+语法特性来构建React的代码。感谢我的同事为这篇文章做出的贡献,特别感谢Babel团队为我们所有人构建一些未来js的特性。