Babel 中文文档
  • 印记中文
  • 文档
  • 配置
  • 试用
  • 视频
  • 博客
  • 赞助
  • 团队
  • GitHub

›All Blog Posts

All Blog Posts

  • 7.18.0 Released: Destructuring private elements and TypeScript 4.7
  • 7.17.0 Released: RegExp 'v' mode and ... 🥁 decorators!
  • 7.16.0 发布: ESLint 8 和 TypeScript 4.5
  • 7.15.0 发布:支持 Hack-style 管道, TypeScript 枚举常量和 Rhino 目标
  • Babel is used by millions, so why are we running out of money?
  • 7.14.0 Released: New class features enabled by default, TypeScript 4.3, and better CommonJS interop
  • 7.13.0 Released: Records and Tuples, granular compiler assumptions, and top-level targets
  • 7.12.0 Released: TypeScript 4.1, strings as import/export names, and class static blocks
  • 7.11.0 Released: ECMAScript 2021 support in preset-env, TypeScript 4.0 support, printing config and the future of `babel-eslint`
  • The State of babel-eslint
  • 7.10.0 Released: Class Fields in preset-env, '#private in' checks and better React tree-shaking
  • 7.9.0 Released: Smaller preset-env output, Typescript 3.8 support and a new JSX transform
  • 7.8.0 Released: ECMAScript 2020, .mjs configuration files and @babel/cli improvements
  • Babel's Funding Plans
  • 7.7.0 Released: Error recovery and TypeScript 3.7
  • 7.6.0 Released: Private static accessors and V8 intrinsic syntax
  • 7.5.0 Released: dynamic import and F# pipelines
  • The Babel Podcast
  • 7.4.0 Released: core-js 3, static private methods and partial application
  • 7.3.0 Released: Named capturing groups, private instance accessors and smart pipelines
  • 7.2.0 发布:私有实例方法(Private Instance Methods)
  • 在 Babel 中支持 TC39 标准的装饰器
  • 7.1.0 Released: Decorators, Private Static Fields
  • Babel 7 发布
  • Removing Babel's Stage Presets
  • What's Happening With the Pipeline (|>) Proposal?
  • Announcing Babel's New Partnership with trivago!
  • On Consuming (and Publishing) ES2015+ Packages
  • Nearing the 7.0 Release
  • Babel Turns Three
  • Planning for 7.0
  • Zero-config code transformation with babel-plugin-macros
  • Contributing to Babel: Three Lessons to Remember
  • Personal Experiences at Babel #1 — A PR with Unusually High Number of Reviews
  • Babel and Summer of Code 2017
  • Upgrade to Babel 7 (moved)
  • Upgrade to Babel 7 for Tool Authors (WIP)
  • 6.23.0 Released
  • The State of Babel
  • 6.19.0 Released
  • 6.18.0 Released
  • 6.16.0 Released
  • Babili (babel-minify)
  • 6.14.0 Released
  • Babel Doctor
  • Setting up Babel 6
  • 6.0.0 Released
  • React on ES6+
  • Function Bind Syntax
  • 5.0.0 Released
  • Babel 喜爱 React
  • 并非出生而逐渐走向灭亡
  • 2to3
  • 6to5 + esnext

React on ES6+

July 7, 2015

Steven Luscher

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

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:

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:

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

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.

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:

// 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
  }
}

Property initializers

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:

// 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 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

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.

// 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});
  },
});

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:

// 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});
  }
}

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

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

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:

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

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:

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

Destructuring & spread attributes

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:

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>
    );
  }
}

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:

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

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

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

Thanks for reading

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.

Recent Posts
  • Classes
  • Property initializers
  • Arrow functions
  • Dynamic property names & template strings
  • Destructuring & spread attributes
  • Thanks for reading
Babel 中文文档
文档
学习 ES2015
社区
视频用户Stack OverflowSlack 频道Twitter
更多
博客GitHub 组织GitHub 仓库Website 仓库旧版网址 6.x旧版网址 5.x