React 在 ES6+ 上
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
今年在对 Instagram Web 进行彻底重构时,我们大量使用了 ES6+ 特性来编写 React 组件。让我重点介绍这些新语言特性如何改变你编写 React 应用的方式,使其变得前所未有的轻松有趣。
类
目前使用 ES6+ 编写 React 组件最显著的变化,体现在我们选择使用 类定义语法 时。不再使用 React.createClass 方法定义组件,我们可以直接定义一个继承自 React.Component 的标准 ES6 类:
class Photo extends React.Component {
render() {
return <img alt={this.props.caption} src={this.props.src} />;
}
}
你会立刻注意到一个细微差异 —— 类定义语法更加简洁:
// The ES5 way
var Photo = React.createClass({
handleDoubleTap: function(e) { … },
render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
handleDoubleTap(e) { … }
render() { … }
}
最明显的变化是:我们移除了两个括号和结尾分号,每个方法声明时省略了冒号、function 关键字和逗号。
除一个方法外,所有生命周期方法都可以按预期在新类语法中定义。类的 constructor 现在承担了原先由 componentWillMount 扮演的角色:
// The ES5 way
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// Operations usually carried out in componentWillMount go here
}
}
属性初始化器
在 ES6+ 的类体系中,属性类型和默认值作为类的静态属性存在。这些属性以及组件的初始状态,都可以通过 ES7 的 属性初始化器 来定义:
// The ES5 way
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,
},
});
// The ES6+ way
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 函数来定义属性默认值和初始状态对象。
箭头函数
React.createClass 方法曾对组件的实例方法执行额外的绑定操作,确保方法内部的 this 关键字指向当前组件实例。
// Autobinding, brought to you by React.createClass
var PostInfo = React.createClass({
handleOptionsButtonClick: function(e) {
// Here, 'this' refers to the component instance.
this.setState({showOptionsModal: true});
},
});
由于使用 ES6+ 类语法定义组件时不会调用 React.createClass 方法,这意味着我们需要手动绑定实例方法才能获得相同行为:
// Manually bind, wherever you need to
class PostInfo extends React.Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
}
handleOptionsButtonClick(e) {
// ...to ensure that 'this' refers to the component instance here.
this.setState({showOptionsModal: true});
}
}
幸运的是,结合 ES6+ 的两项特性 —— 箭头函数 和属性初始化器 —— 可以轻松实现组件实例的按需绑定:
class PostInfo extends React.Component {
handleOptionsButtonClick = (e) => {
this.setState({showOptionsModal: true});
}
}
ES6 箭头函数体与其外围代码共享相同的词法作用域 this,加上 ES7 属性初始化器的作用域机制,共同实现了我们期望的效果。查看实现原理了解具体工作机制。
动态属性名与模板字符串
对象字面量的增强功能 包含了对派生属性名的赋值能力。过去我们可能需要这样设置状态片段:
var Form = React.createClass({
onChange: function(inputName, e) {
var stateToSet = {};
stateToSet[inputName + 'Value'] = e.target.value;
this.setState(stateToSet);
},
});
现在,我们能够构建在运行时通过 JavaScript 表达式确定属性名的对象。这里使用**模板字符串**来确定要设置哪个状态属性:
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}
解构与展开属性
在组合组件时,我们通常希望将父组件大部分属性传递给子组件,但并非全部。结合 ES6+ 的**解构和 JSX 的展开属性**,我们可以轻松实现:
class AutoloadingPostsGrid extends React.Component {
render() {
const {
className,
...others // contains all properties of this.props except for className
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}
我们还可以将 JSX 展开属性与常规属性结合使用,利用简单的优先级规则实现覆盖和默认值。该元素仍将获得 className "override",即使 this.props 中存在 className 属性:
<div {...this.props} className="override">
…
</div>
该元素通常具有 className “base”,除非 this.props 中存在 className 属性将其覆盖:
<div className="base" {...this.props}>
…
</div>
感谢阅读
希望您和我们一样享受使用 ES6+ 特性编写 React 代码的乐趣。感谢我的同事对本文的贡献,特别感谢 Babel 团队让我们在今天就能拥抱未来。