Skip to main content

在 ES6+ 上使用 React

· 9 min read

在今年从头到尾重设计 Instagram Web 的过程中,我们很享受使用许多 ES6+ 特性来编写我们的 React 组件。让我来强调一下这些新语言特性的一些用法,它们可以改变你编写 React 应用的方式,让开发变得比以往更简单、更有趣。

🌐 While redesigning Instagram Web from the inside out this year, we enjoyed using a number of ES6+ features to write our React components. Allow me to highlight some of the ways that these new language features can change the way you write a React app, making it easier and more fun than ever.

类​

🌐 Classes

迄今为止,使用 ES6+ 编写 React 组件时最明显的变化,是当我们选择使用 类定义语法。我们不再使用 React.createClass 方法来定义组件,而是可以定义一个真正的 ES6 类,并继承自 React.Component:

🌐 By far the most outwardly visible change to how we write React components using ES6+ comes about when we choose to use the class definition syntax. Instead of using the React.createClass method to define a component, we can define a bonafide ES6 class that extends React.Component:

JavaScript
class Photo extends React.Component {
render() {
return <img alt={this.props.caption} src={this.props.src} />;
}
}

你会立刻注意到一个细微的差别——在定义类时,你可以使用更简洁的语法:

🌐 Right away, you'll notice a subtle difference – a more terse syntax is available to you when defining classes:

JavaScript
// The ES5 way
var Photo = React.createClass({
handleDoubleTap: function(e) { … },
render: function() { … },
});
JavaScript
// The ES6+ way
class Photo extends React.Component {
handleDoubleTap(e) { … }
render() { … }
}

值得注意的是,我们去掉了两个括号和一个末尾的分号,并且对于每个声明的方法,我们省略了冒号、function 关键字和一个逗号。

🌐 Notably, we've dropped two parentheses and a trailing semicolon, and for each method declared we omit a colon, a function keyword, and a comma.

除了一个之外,所有的生命周期方法都可以像使用新的类语法时那样定义。类的 constructor 现在承担了之前由 componentWillMount 扮演的角色:

🌐 All of the lifecycle methods but one can be defined as you would expect when using the new class syntax. The class' constructor now assumes the role previously filled by componentWillMount:

JavaScript
// The ES5 way
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
JavaScript
// The ES6+ way
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// Operations usually carried out in componentWillMount go here
}
}

属性初始化器​

🌐 Property initializers

在 ES6+ 的类世界里,属性类型和默认值作为静态属性存在于类本身。这些,以及组件的初始状态,都可以用 ES7 属性初始化器 来定义:

🌐 In the ES6+ class world, prop types and defaults live as static properties on the class itself. These, as well as the component's initial state, can be defined using ES7 property initializers:

JavaScript
// 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,
},
});
JavaScript
// 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 函数来定义属性默认值和初始状态对象。

🌐 ES7 property initializers operate inside the class' constructor, where this refers to the instance of the class under construction, so the initial state can still be made to depend on this.props. Notably, we no longer have to define prop defaults and the initial state object in terms of a getter function.

箭头函数​

🌐 Arrow functions

React.createClass 方法用于对组件的实例方法执行一些额外的绑定工作,以确保在这些方法内部,this 关键字会引用该组件的实例。

🌐 The React.createClass method used to perform some extra binding work on your component's instance methods to make sure that, inside them, the this keyword would refer to the instance of the component in question.

JavaScript
// 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 方法,所以似乎需要在希望使用这种行为的地方手动绑定实例方法:

🌐 Since we don't involve the React.createClass method when we define components using the ES6+ class syntax, it would seem that we need to manually bind instance methods wherever we want this behavior:

JavaScript
// 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+ 特性——箭头函数 和属性初始化器——选择性绑定到组件实例变得轻而易举:

🌐 Luckily, by combining two ES6+ features – arrow functions and property initializers – opt-in binding to the component instance becomes a breeze:

JavaScript
class PostInfo extends React.Component {
handleOptionsButtonClick = (e) => {
this.setState({showOptionsModal: true});
}
}

ES6 箭头函数的函数体共享与其周围代码相同的词法 this,这让我们得到了期望的结果,因为 ES7 属性初始化器的作用域方式就是这样。看看内幕 来看看为什么这会起作用。

🌐 The body of ES6 arrow functions share the same lexical this as the code that surrounds them, which gets us the desired result because of the way that ES7 property initializers are scoped. Peek under the hood to see why this works.

动态属性名和模板字符串​

🌐 Dynamic property names & template strings

对象字面量的**增强功能**之一包括可以给派生的属性名赋值。我们可能最初会这样做来设置一部分状态:

🌐 One of the enhancements to object literals includes the ability to assign to a derived property name. We might have originally done something like this to set a piece of state:

JavaScript
var Form = React.createClass({
onChange: function(inputName, e) {
var stateToSet = {};
stateToSet[inputName + 'Value'] = e.target.value;
this.setState(stateToSet);
},
});

现在,我们可以构建对象,其属性名由运行时的 JavaScript 表达式决定。这里,我们使用 模板字符串 来确定要在 state 上设置哪个属性:

🌐 Now, we have the ability to construct objects whose property names are determined by a JavaScript expression at runtime. Here, we use a template string to determine which property to set on state:

JavaScript
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}

解构与展开属性​

🌐 Destructuring & spread attributes

在编写组件时,我们常常希望将父组件的大部分 props 传递给子组件,但不是全部。结合 ES6+ 的 解构 和 JSX 的 展开属性,可以轻松实现这一点:

🌐 Often when composing components, we might want to pass down most of a parent component's props to a child component, but not all of them. In combining ES6+ destructuring with JSX spread attributes, this becomes possible without ceremony:

JavaScript
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 的展开属性与普通属性结合使用,利用一个简单的优先规则来实现覆盖和默认值。即使 this.props 中存在 className 属性,这个元素也会获得 className 的“覆盖”属性:

🌐 We can combine JSX spread attributes with regular attributes too, taking advantage of a simple precedence rule to implement overrides and defaults. This element will acquire the className “override” even if there exists a className property in this.props:

JavaScript
<div {...this.props} className="override">
…
</div>

这个元素通常会有 className “基础”,除非在 this.props 中存在 className 属性来覆盖它:

🌐 This element will regularly have the className “base” unless there exists a className property in this.props to override it:

JavaScript
<div className="base" {...this.props}>
…
</div>

谢谢阅读​

🌐 Thanks for reading

我希望你像我们一样喜欢使用 ES6+ 语言特性来编写 React 代码。感谢我的同事们对这篇文章的贡献,特别感谢 Babel 团队,让我们今天就能使用面向未来的技术。

🌐 I hope that you enjoy using ES6+ language features to write React code as much as we do. Thanks to my colleagues for their contributions to this post, and thanks especially to the Babel team for making the future available to all of us, today.