ES5のReact.jsソースをES6ベースに書き換える

# 概要
開発者にとってはメリットがありそうだけどユーザーにとってはいまいちメリットがよく分からないES6。ひとまずどのくらい開発者にとってメリットがあるのかを実際に把握するため、ES5のReact.jsソースをES6ベースに書き換えてみました。

対象のソースは、投稿[[react-router v2.xとcontext](http://qiita.com/kuniken/items/b8777c8342fe1b4ff727)]のサンプルソース（[https://github.com/kunitak/react-router-1to2](https://github.com/kunitak/react-router-1to2)）です。

# 参考
React TutorialをES6で書きなおしてみた - console.blog(self); 
[http://sadah.hatenablog.com/entry/2015/08/03/085828](http://sadah.hatenablog.com/entry/2015/08/03/085828)

Babelで理解するEcmaScript6の import / export
[http://qiita.com/inuscript/items/41168a50904242005271](http://qiita.com/inuscript/items/41168a50904242005271)

本家サイト
[https://facebook.github.io/react/docs/reusable-components.html#es6-classes](https://facebook.github.io/react/docs/reusable-components.html#es6-classes)

# ES6ベースに書き換えてみる
## Babelのインストール
babelifyと必要なプリセットをインストールしました。

```
$ npm install --save babelify babel-preset-es2015 babel-preset-react
```

## ソースの改修
client/scripts以下のソースをES6ベースに改修していきます。

### importの改修

requireを使ってモジュールをimportしていた箇所をimportを使って定義していきます。

#### 単純なパターン

```file:before
var React = require('react');
```

```file:after
import React from 'react';
```
これだけ。

#### モジュール内のメンバをimportする場合

```file:before
var EventEmitter = require('events').EventEmitter;
```

```file:after
import {EventEmitter} from 'events';
```
{}で囲みます。別名で扱いたい場合は、asをつけて変数を宣言してあげます。

```file:after
import {EventEmitter as Emitter} from 'events';
```

同一モジュールから複数のメンバをまとめてimportすることもできます。

```file:before
var ReactRouter = require('react-router'),
    Router = ReactRouter.Router,
    Route = ReactRouter.Route,
    IndexRoute = ReactRouter.IndexRoute,
    History = ReactRouter.History,
    hashHistory = ReactRouter.hashHistory;
```

```file:after
import {Router, Route, IndexRoute, History, hashHistory} from 'react-router';
```

### コンポーネント定義の改修
ES5ではコンポーネント定義はcreateClassを使って定義していました。

しかし、公式ページ
[https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html](https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html)
では、
>Our eventual goal is for ES6 classes to replace React.createClass completely,
（我々の最終目標はES6のクラスからReact.createClassを完全になくすことだ）

と述べており、本家サイト（es6-classes）が提示しているように、ES6ではcreateClassではなく、Componentの継承により定義したいと思います。

```file:ES5
var Index = React.createClass({
  render: function(){
    return (
      <div>
        {this.props.children}
      </div>
    );
  }
});
```

```file:ES6
class Index extends React.Component{
  render(){
    return (
      <div>
        {this.props.children}
      </div>
    );
  }
}
```
classとして、React.Componentを継承して定義しています。
そのため、extends使って次のようなこともできます。

```
//ボディの定義
class Body extends React.Component{
  constructor(props) {
    super(props);
    this.state = {message: ''}; //getInitialStateの代わりにconstructorで設定する
  }
  
  render(){
    return (
      <h1>ポータル {this.state.message}</h1>
    );
  }
}

//継承：ボディの定義
class ParentBody extends Body{
  componentDidMount() {
    this.setState({message: 'huga'});//画面では[ポータル huga]と表示される
  }
}
```

さて、createClassでは定義したことのない、constructorというメソッドが出てきました。

### "React.createClass" vs "extends React.Component"
React.Componentを使用した場合、幾つかcreateClassとは異なる仕様にぶち当たります。
以下のような違いがありました。


|                       | React.createClass | React.Component |
|:----------------------|:-----------------:|:---------------:|
| thisのオートバインディング　| ○ <br/> 全てのメソッドにthisをバインドしてくれる | × <br/> constructorでthisをバインドするように記述する必要がある |
| mixins利用可否        | 可 | 不可 |
| state初期化           | getInitialStateメソッドを使用して初期化 | constructorでobjectの直接代入により初期化（getInitialStateメソッドは存在しない） |
| props初期値指定        | getDefaultPropsメソッドを使用して指定 | コンポーネント外部でdefaultPropsオブジェクトに代入する(getDefaultPropsメソッドは存在しない) |
| protoTypes、contextTypes定義  | コンポーネント内で定義 | コンポーネント外部でprotoTypes、contextTypesオブジェクトに代入する |

React.createClassはコンポーネントのインスタンス時に色々やってくれていたということがわかりました。もちろんその時にReact.Componentも取り込んでいました。

反対の視点で言えば、実装者にとって、いらないこともやっている、という見方もできます。

React.Componentのみでの宣言は、そういったある意味余計な処理は一切利用せず、実装者が都合に合わせて記述できる、というメリットがあると思います（後で調べたら[本家サイト](https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html)で「開発者は柔軟にコンポーネント実装ができるようになる」というようなことを述べていました）。

ちなみにmixinsについては、今後もES6での対応は予定していないとのこと。
[https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#mixins](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#mixins)


では、引き続きReact.Componentで実装していきます。

### constructorの実装
#### superクラスのconstructor実装
constructorの実装は必須ではないのですが、何らかの理由で自分で実装する場合は、superクラスのconstructorも実装してあげる必要があります。

```
constructor(props) {
  super(props);
  //anything to do
}
```

#### contextを利用する場合
contextを利用していて、constructorで何かしたい場合は、以下のように書けば良いようです。

```
constructor(props, context) {
  super(props, context);
  //anything to do
}
```

#### state初期化
getInitialStateメソッドがないので、this.stateに直接オブジェクトを代入します。

```
constructor(props) {
  super(props);
  this.state = {message: ''}; //getInitialStateの代わりにconstructorで設定する
}

componentDidMount() {
  this.setState({message: 'huga'});
}

render(){
  return (
    <h1>ポータル {this.state.message}</h1>
  );
}

```

#### カスタムメソッドへのthisのバインド
stateやprops、refsなどを扱うメソッドを自分で実装する場合は、thisをそのメソッドにバインドしてあげる必要があります。バインドしないとthisは当然nullのためエラーになります。

```
constructor(props) {
  super(props);
  this.handleSubmit = this.handleSubmit.bind(this);//バインド
  this.state = {message: ''}; 
}

handleSubmit(){
  var name = ReactDOM.findDOMNode(this.refs.name).value.trim();
  var mail = ReactDOM.findDOMNode(this.refs.mail).value.trim();
  this.props.addUser(name, mail);
}

componentDidMount() {
  this.setState({message: 'huga'});
}

```

### props初期値指定、protoTypes、contextTypes定義
これらはコンポーネント定義後に外部で実装します。

#### props初期値指定、protoTypes定義

```
class User extends React.Component{
  render(){
    return (
      <tr>
        <td>{this.props.name}</td>
        <td>{this.props.mail}</td>
      </tr>
    );
  }
}
//propTypesは外で定義する
User.propTypes = {
  name: React.PropTypes.string.isRequired,
  mail: React.PropTypes.string
};
//props初期値指定
User.defaultProps = {name: "hoge"};
```

#### contextTypes定義

```
class Header extends React.Component{
  render(){
    return (
      <header>
        <Link to="/portal" style={{paddingRight: "5px"}}>ポータル</Link>
      </header>
    );
  }
};
//contextTypesは外で定義する
Header.contextTypes = {
  router: React.PropTypes.object.isRequired
}
```

### varをlet、constに改修する
ES6からlet、constで変数宣言できるようになっているのでvarを改修しました。

ちなみに違いは以下のとおりです。

- let
  - 同一スコープでの再定義が不可（varは可能）。
- const
  - 再代入も再定義も不可。

詳細を知りたい方は以下でどうぞ。
[http://solutionware.jp/blog/2016/01/08/es6の新機能を学ぶ-constキーワードとletキーワード/](http://solutionware.jp/blog/2016/01/08/es6の新機能を学ぶ-constキーワードとletキーワード/)

一回代入したらそのまま使って終わりな変数が大部分なので、だいたいconstになってしまいそうです。letはfor文のiとかで使うケースが多いのかな。

```
handleSubmit(){
  const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
  const mail = ReactDOM.findDOMNode(this.refs.mail).value.trim();
  this.props.addUser(name, mail);
}
```

### アロー関数の導入
以下な感じです。

```file:before
var getUserStoreStates = function(){
  return UserStore.getAjaxResult();
};
```

```file:after
const getUserStoreStates = () => UserStore.getAjaxResult();
```
returnを書かなくても値を返してくれます。

複数行にわたる場合は、{}で囲んであげます。

```
const ajax = {
  get  : (url, params, callback) => {
    request
      .get(url)
      .query(params)
      .end((err, res) => callback(err, res))
  },
  post : (url, params, callback) => {
    request
      .post(url)
      .send(params)
      .end((err, res) => callback(err, res))
  }
};
```

### exportの改修
ついでにexportも改修しました。

```file:before
module.exports = UserBox;
```

```file:after
export default UserBox;
```

## まとめ
書き換え作業を通じて思ったメリット・デメリットは以下のとおりでした。

### メリット
- スキルトランスファーしやすい：
 - 例：javaエンジニアでも理解しやすい構造になってきている。importとかclass、extendsとか知ってる単語が出てくる。
- extendsで簡単に既存のクラスが継承できるので、開発効率が上がる。
- アロー関数により、thisをthatに代入して、とか素人目で意味不明なことをしなくて済む。

### デメリット
- es6で書いて、babelかますと、es5で書くよりサイズがでかくなってしまう。
  - es5で解釈できるようにするために、babelify時に無駄にメソッドが増えてしまうため。
  - また、実装時のコードはbabelifyにより若干改変されることを頭に入れてデバッグする必要がある。（許容できるレベルではある）
- ソースの管理コストを考えると、当然gulpタスクもnode.jsもes6ベースで書くことを考えねばならない。
- 他のプロジェクトは、、まあいいか。

来る日に備えて手始めに社内ツールから始めるというのも手かなあと感じました。

# サンプルソース
[https://github.com/kunitak/es5-to-es6](https://github.com/kunitak/es5-to-es6)

[ES5とES6のソース比較](https://github.com/kunitak/es5-to-es6/compare/es5...es6)

# 補足
[続：ES5のReact.jsソースをES6ベースに書き換える](http://qiita.com/kuniken/items/1200e277d0ebf7f0a68a)