React Tutorial Example (ECMAScript 2015)


[Redux](http://redux.js.org/) は [ECMAScript 2015](http://www.ecma-international.org/ecma-262/6.0/index.html) の文法でコードを実装すると効果的です。
[ECMAScript 2015(ES2015)](http://babeljs.io/docs/learn-es2015/) は **JavaScript** に新しい *シンタックス* や *オブジェクト* が追加されています。
[前回](http://qiita.com/ogomr/items/64aae7c8d6a113608230) のアプリケーションを ES2015 に変更してみましょう。

## Learning

* ES2015 の *Import, Export* を利用する。
* ES2015 の *Class, Extends* を利用する。
* ES2015 の *Function, Default parameter* を利用する。

## Environment

* node: v4.4.5
* npm: v3.9.6

## Comment Box Form

* 完成される [Source Code](https://github.com/ogom/react-comment-box-example/tree/es2015) のファイルリストです。

```bash
$ tree -a -I node_modules
.
├── .babelrc
├── app.js
├── index.html
├── index.js
├── package.json
├── style.css
└── webpack.config.js
```

*Let's hands-on*

### Setup application

* `git clone` コマンドでアプリケーションをダウンロードします。
* `npm install` コマンドで依存するモジュールをインストールします。

```bash
$ git clone https://github.com/ogom/react-comment-box-example.git
$ cd react-comment-box-example
$ git checkout redux
$ npm install
```

### Start HTTP Server

* `npm start` コマンドで *Webアプリケーション* を実行します。
* ブラウザで http://localhost:4000 をロードすると **Comment Box Example** が表示されます。

```bash
$ npm start
$ open http://localhost:4000
```

(API Server は [React Tutorial Example (Express)](http://qiita.com/ogomr/items/b90c68764c3c75cf501e) をご利用ください。)

### Remove semicolons

* まずは `index.js` と `app.js` の `;` を削除します。
    * Webpack で bundle すると `;` は記述されるので *Source Code* には不要です。
    * ちなみに [Redux の Examples](https://github.com/reactjs/redux/tree/master/examples) に `;` は記述されていません。


### Add Babel preset es2015 package

* Babel preset es2015 のモジュールをインストールします。

```bash
$ npm install babel-preset-es2015 --save-dev
```

### Add babelrc

* Babel preset に ES2015 plugin を設定します。

```diff:.babelrc
 {
-  "presets": ["react"]
+  "presets": ["es2015", "react"]
 }
```

### ES2015

* [Learn ES2015](http://babeljs.io/docs/learn-es2015/) で *ECMAScript 2015* の  基本を学習できます。
* [Mozilla Developer Network](https://developer.mozilla.org/ja/docs/Web/JavaScript) は *JavaScript* を幅広く学習できます。

#### Containers

つぎは コンテナの `index.js` から ES2015 に変更します。

##### Import

* `require` を `import` に変更します。
    * [import](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import) はモジュールをインポートします。
        * `import Example from 'example'` はモジュールの全コンテンツがインポートされます。
        * `import { foo, bar } from 'example'` はモジュールの対象のメンバーがインポートされます。

```diff:index.js
-var React = require('react')
-var ReactDOM = require('react-dom')
-var CommentBox = require('./app')
+import React from 'react'
+import ReactDOM from 'react-dom'
+import CommentBox from './app'

-var Redux = require('redux')
-var createStore = Redux.createStore
+import { createStore } from 'redux'

-var ReactRedux = require('react-redux')
-var Provider = ReactRedux.Provider
+import { Provider } from 'react-redux'
```

##### Const

* `var` を `const` に変更します。
    * [const](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const) は読み取り専用の値を生成します。

```diff:index.js
-var store = createStore(function(state, action) {
+const store = createStore(function(state, action) {
```

##### Function

* `function(state, action)` を `(state, action) =>` に変更します。
    * [アロー関数式](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/arrow_functions) は *function式* より短い構文です。

```diff:index.js
-const store = createStore(function(state, action) {
+const store = createStore((state, action) => {
```

##### Default parameter values

* `(state, action)` を `(state=[], action)` に変更します。
    * [引数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions_and_function_scope/Default_parameters) が *undefined* の場合に初期値を設定します。

```diff:index.js
-const store = createStore((state, action) => {
-  if (state === undefined) {
-    return []
-  }
+const store = createStore((state=[], action) => {
```

*ノイズになっていたコードが削がれて、実装したいコードに集中できるようになりました！*

#### Components

さらに コンポーネントの `app.js` を ES2015 に変更します。
(以降は、さきほどのコンテナで紹介したシンタックスの説明は省略します。)

##### Import

* `require` を `import` に変更します。

```diff:app.js
-var React = require('react')
-var Remarkable = require('remarkable')
-var $ = require('jquery')
+import React from 'react'
+import Remarkable from 'remarkable'
+import $ from 'jquery'

-var ReactRedux = require('react-redux')
-var connect = ReactRedux.connect
+import { connect } from 'react-redux'
```

##### Export

* `module.exports` を `export default` に変更します。
  * [export](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import) はモジュールをエクスポートします。

```diff:app.js
-module.exports = connect(mapStateToProps, mapDispatchToProps)(CommentBox)
+export default connect(mapStateToProps, mapDispatchToProps)(CommentBox)
```

##### Function

* `showComments: function(comments)` を `showComments(comments)` に変更します。
* `addComment: function(comments)` を `addComment(comment)` に変更します。
    * [メソッド定義](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions) が短い構文になりました。

```diff:app.js
-var mapStateToProps = function(state) {
+const mapStateToProps = (state) => {
   return {data: state}
 }

-var mapDispatchToProps = function(dispatch) {
+const mapDispatchToProps = (dispatch) => {
   return {
-    showComments: function(comments) {
+    showComments(comments) {
       dispatch({type: 'show_comments', comments: comments})
     },
-    addComment: function(comment) {
+    addComment(comment) {
       dispatch({type: 'add_comment', comment: comment})
     }
   }
 }
```

#####  Class

###### Comment

* React の Component をインポートします。
* `Comment = React.createClass` を `class Comment extends Component` に変更します。
    * [Component](https://facebook.github.io/react/docs/reusable-components.html#es6-classes) を継承して Comment [class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/class) を定義します。

```diff:app.js
-import React from 'react'
+import React, { Component } from 'react'

-var Comment = React.createClass({
-  rawMarkup: function() {
-    var md = new Remarkable()
-    var rawMarkup = md.render(this.props.children.toString())
+class Comment extends Component {
+  rawMarkup() {
+    const md = new Remarkable()
+    const rawMarkup = md.render(this.props.children.toString())
     return { __html: rawMarkup }
-  },
+  }

-  render: function() {
+  render() {
     return (
       <div className="comment">
         <h2 className="commentAuthor">
           {this.props.author}
         </h2>
         <span dangerouslySetInnerHTML={this.rawMarkup()} />
       </div>
     )
   }
-})
+}
```

###### CommentList

* `CommentList = React.createClass` を `class CommentList extends Component` に変更します。

```diff:app.js
-var CommentList = React.createClass({
-  render: function() {
-    var commentNodes = this.props.data.map(function(comment) {
+class CommentList extends Component {
+  render() {
+    const commentNodes = this.props.data.map((comment) => {
       return (
         <Comment author={comment.author} key={comment.id}>
           {comment.text}
         </Comment>
       )
     })
     return (
       <div className="commentList">
         {commentNodes}
       </div>
     )
   }
-})
+}
```

###### CommentForm

* `CommentForm = React.createClass` を `class CommentForm extends Component` に変更します。
* `getInitialState` を `constructor` に変更します。
  * `constructor` はクラスの [コンストラクタ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) です。
* `this.handleSubmit` に `bind(this)` を追加します。
* `this.handleAuthorChange` に `bind(this)` を追加します。
* `this.handleTextChange` に `bind(this)` を追加します。

```diff:app.js
-var CommentForm = React.createClass({
-  getInitialState: function() {
-    return {author: '', text: ''}
-  },
-  handleAuthorChange: function(e) {
+class CommentForm extends Component {
+  constructor(props) {
+    super(props)
+    this.state = {author: '', text: ''}
+  }
+
+  handleAuthorChange(e) {
     this.setState({author: e.target.value})
-  },
+  }
+
-  handleTextChange: function(e) {
+  handleTextChange(e) {
     this.setState({text: e.target.value})
-  },
+  }
+
-  handleSubmit: function(e) {
+  handleSubmit(e) {
     e.preventDefault()
-    var author = this.state.author.trim()
-    var text = this.state.text.trim()
+    const author = this.state.author.trim()
+    const text = this.state.text.trim()
     if (!text || !author) {
       return
     }
     this.props.onCommentSubmit({author: author, text: text})
     this.setState({author: '', text: ''})
-  },
+  }
+
-  render: function() {
+  render() {
     return (
-      <form className="commentForm" onSubmit={this.handleSubmit}>
+      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
         <input
           type="text"
           placeholder="Your name"
           value={this.state.author}
-          onChange={this.handleAuthorChange}
+          onChange={this.handleAuthorChange.bind(this)}
         />
         <input
           type="text"
           placeholder="Say something..."
           value={this.state.text}
-          onChange={this.handleTextChange}
+          onChange={this.handleTextChange.bind(this)}
         />
         <input type="submit" value="Post" />
       </form>
     )
   }
-})
+}
```

###### CommentBox

* `CommentBox = React.createClass` を `class CommentBox extends Component` に変更します。
* `getInitialState` を `constructor` に変更します。
  * `constructor` はクラスの [コンストラクタ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) です。
* `this.loadCommentsFromServer` に `bind(this)` を追加します。
* `this.handleCommentSubmit` に `bind(this)` を追加します。

```diff:app.js
-var CommentBox = React.createClass({
-  getInitialState: function() {
-    return {data: []}
-  },
-  loadCommentsFromServer: function() {
+class CommentBox extends Component {
+  constructor(props) {
+    super(props)
+    this.state = {data: []}
+  }
+
+  loadCommentsFromServer() {
     $.ajax({
     })
-  },
+  }
+
-  handleCommentSubmit: function(comment) {
-    var comments = this.state.data
+  handleCommentSubmit(comment) {
+    const comments = this.state.data
-  },
+  }
+
-  componentDidMount: function() {
+  componentDidMount() {
     this.loadCommentsFromServer()
-    setInterval(this.loadCommentsFromServer, this.props.pollInterval)
+    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval)
-  },
+  }
+
-  render: function() {
+  render() {
     return (
       <div className="commentBox">
         <h1>Comments</h1>
         <CommentList data={this.props.data} />
-        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
+        <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
       </div>
     )
   }
-})
+}
```

*Congrats!*

----

ES2015 に変更することで可読性の高いコードになりました。
次は [Export と Import を利用してコードをファイルを分離](http://qiita.com/ogomr/items/e6e702633c4f49a1893a) しましょう!