ReactのチュートリアルをES6で書いてwebpackとESLintも使ってみる


*初心者(ReactもJSもES6も)の書いた記事なので色々間違えていたりすると思います。そういうところは助言をいただけるとありがたいです。*

学習として [React](https://facebook.github.io/react/docs/tutorial.html) のコードをES6(2015)で使えるようになった記法で書いてみることにしました。スタイルは [AirbnbのReact/JSXスタイルガイド](https://github.com/airbnb/javascript/tree/master/react) を参考にやろうと思います。

さらに、出力されるJSを**bundle.js**とかいう感じでまとめたくて、 [webpack](https://webpack.github.io/) を使いました。コードもある程度補正してもらいたいと思い、 [ESLint](http://eslint.org/) も使ってみることにしました。

## 準備

まずは、各種ライブラリを入れたりします。React

```bash
npm install --save react react-dom
```

### ESLint

Linterです。細かくルールの設定ができて使っていて気持ち良かったです。最近人気？

```bash
$ npm install -g eslint
$ eslint -v
$ eslint --init
```

`eslint --init` すると対話的に環境を聞いてくれてファイルを作ってくれるのでありがたいです。React使いますかとか聞かれたりします。そしてできたファイルを書きながら更新して行ってこんな感じになりました。

ES6を利用するために [Babel](https://babeljs.io/) を使っているのですが、それも考慮した設定です。

```json:.eslintrc.json
{
  "rules": {
    "indent": [
      2,
      2
    ],
    "quotes": [
      2,
      "double"
    ],
    "linebreak-style": [
      2,
      "unix"
    ],
    "semi": [
      2,
      "always"
    ],
    "no-unused-vars": [
      1, {"vars": "all", "args": "after-used"}
    ],
    "no-console": 1
  },
  "env": {
    "es6": true,
    "browser": true
  },
  "extends": "eslint:recommended",
  "ecmaFeatures": {
    "jsx": true,
    "experimentalObjectRestSpread": true,
    "modules": true
  },
  "plugins": [
    "react"
  ]
}
```

Atomで書いているのですが、 [linter-eslint](https://atom.io/packages/linter-eslint) を入れることでライブでチェックしてくれます。ES6も見てくれるということで（？）ESLintを使ってみることにしました。チェックを走らせたくないファイルは `.eslintignore` に書いておきます。

```text:.eslintignore
node_modules/
test/
webpack.config.js
dist/
server/
```

### Webpack

複数のjavascriptファイルを一つにまとめてくれたり、リソースを良い形にしてくれる（？）ツールです。僕はこれで `bundle.js` が作りたかった（憧れ）ので使ってみました。

[Babel](https://babeljs.io/) を利用するための設定も色々必要です。

```bash
$ npm install --save-dev webpack babel-loader babel-core babel-preset-react babel-preset-es2015
```

- [babel/babel-loader](https://github.com/babel/babel-loader)

**webpack.config.js** は [JavaScript - webpack+babel環境でフロントエンドもES6開発](http://qiita.com/HayneRyo/items/74892d3a37ee96a5df60) を参考にさてていただきました。といっても、やりながらだいぶ変わりました。最終的にはこのようになりました。testのところが二箇所あるのがちょっと微妙です。

```js:webpack.config.js
module.exports = {
  entry: __dirname + "/src/app.js",
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: "babel",
        query:{
          presets: ['react', 'es2015']
        }
      }
    ]
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
};
```

### 簡易サーバーと自動リロード

今回はwebpackでjsの変更を監視しつつ、変更があったら簡易サーバーで立ち上げたページが自動更新するという風にしたいと思います。そのために、 [lite-server](https://github.com/johnpapa/lite-server) と [concurrently](https://www.npmjs.com/package/concurrently) を追加します。

```bash
$ npm install lite-server concurrently --save-dev
```

**package.json** のところに、lite-serverとwebpackとconcurrentlyに関する記述を追加します。 `npm start` するとes6をjsにしてさらにウェブページが更新されます。

Gulpを使わないでnpmで簡易的なことができたらいいなと思ってこのようにしましたが、自信は全くないです。素直にGulpを使ったら良いのかもしれません。この辺りは、 [How to Use npm as a Build Tool](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/) の影響を受けました。

```json:package.json
{
  "name": "9.react-tutorial-2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "marked": "^0.3.5",
    "react": "^0.14.6",
    "react-dom": "^0.14.6",
    "superagent": "^1.6.1"
  },
  "devDependencies": {
    "babel-core": "^6.4.0",
    "babel-loader": "^6.2.1",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "eslint": "^1.10.3",
    "eslint-config-standard": "^4.4.0",
    "eslint-plugin-react": "^3.14.0",
    "eslint-plugin-standard": "^1.3.1",
    "lite-server": "^1.3.2",
    "webpack": "^1.12.10"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack -w",
    "lite": "lite-server --verbose --open dist",
    "start": "concurrent \"npm run webpack\" \"npm run lite\""
  },
  "author": "",
  "license": "ISC"
}
```

## Reactのチュートリアルを書いてみる

さて、環境が整ったところでReactもう一度チュートリアルを読みながら書いていきます。気になったところだけコメントを書いてそれ以外は、コードをすべて貼り出したのでそれで語ります。ちなみに、サーバー側のコードは [Tutorial](https://facebook.github.io/react/docs/tutorial.html) をそのまま（ちょっとだけ手を入れて）使っています。

Githubにもpushしてます。

- [js-study/9.react-tutorial-2 at master · morizotter/js-study](https://github.com/morizotter/js-study/tree/master/9.react-tutorial-2)

### index.html

この `bundle.js` がやりたかったんですよね。できて良かった！それから、 `<script src="...">` ってちょっと前に書いた時は書きまくってたんだけど、それもwebpackを使うことで書く必要がなくなりました。 `import React from "react";` というように書いておけばビルドするときに解決してくれる様子！

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="text/javascript" src="dist/bundle.js"></script>
  </body>
</html>
</pre>
```

### app.js

チュートリアルにはないけれど、CommentBoxから分離してみました。全体の設定とかこのファイルでできるかな？

```js:app.js
import React from "react";
import ReactDOM from "react-dom";
import CommentBox from "./CommentBox";

ReactDOM.render(
  <CommentBox url="http://localhost:3001/api/comments" pollInterval={2000}/>,
  document.getElementById("app")
);
```

### CommentBox.jsx

通信は、jQueryではなく [superagent](https://www.npmjs.com/package/superagent) というのを使ってみました。分かりやすくて良いと思いました。 `bind(this)` しないとよばれた関数の中でクラス自体にアクセスできないというのは、たぶん、JS初心者がみな通る道なんでしょうね...。

```js:CommentBox.jsx
import React from "react";
import request from "superagent";
import CommentList from "./CommentList";
import CommentForm from "./CommentForm";

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };

    this.loadCommentsFromServer = this.loadCommentsFromServer.bind(this);
  }

  loadCommentsFromServer() {
    request
      .get(this.props.url)
      .end((err, res) => {
        if (err) {
          throw err;
        }
        this.setState({data: res.body});
      });
  }

  handleCommentSubmit(comment) {
    var comments = this.state.data;
    comment.id = Date.now();
    var newComments = comments.concat([comment]);
    this.setState({data: newComments});
    request
      .post(this.props.url)
      .send(comment)
      .end((err, res) => {
        if (err) {
          this.setState({data: comments});
          throw err;
        }
        this.setState({data: res.body});
      });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data}/>
        <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
      </div>
    );
  }
}

```

### CommentList.jsx

```js:CommentList.jsx
import React from "react";
import Comment from "./Comment";

export default class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  
  render() {
    var commentNodes = this.props.data.map((comment) => {
      return (
        <Comment author={comment.author} key={comment.id}>
          {comment.text}
        </Comment>
      );
    });

    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
}
```

### CommentForm.jsx

```js:CommentForm.jsx
import React from "react";

export default class CommentForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      author: "",
      text: ""
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleAuthorChange = this.handleAuthorChange.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
  }

  handleAuthorChange(e) {
    this.setState({author: e.target.value});
  }

  handleTextChange(e) {
    this.setState({text: e.target.value});
  }

  handleSubmit(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }

    this.props.onCommentSubmit({author: author, text: text});
    this.setState({author: "", text: ""});
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
          />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
          />
        <input type="submit" value="Post" />
      </form>
    );
  }
}

```

### Comment.jsx

```js:Comment.jsx
import React from "react";
import marked from "marked";

export default class Comment extends React.Component {
  rawMarkup() {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup};
  }
  
  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
}
```

### はまった

```js:CommentBox.jsx
import CommentList from "./CommentList.jsx";
import CommentForm from "./CommentForm.jsx";
```

他のファイルを読み込む際に拡張子を入れないとモジュールが見つからないと言われました。 [Suggestion: Webpack require should resolve jsx by default · Issue #16 · newtriks/generator-react-webpack](https://github.com/newtriks/generator-react-webpack/issues/16) によるとresolve...が必要ということだったのでつけたらうまくいきました（冒頭のwebpackの設定ファイルに反映しています）。

ESLintのおかげで記法もそんなにぶれずに書けたと思います。ただし、正しい記法とかもっと良い記法はもちろんあると思います。JS慣れてないので。。。

## 今回の学習で利用したサービス・ツールなど

- [Getting Started | React](https://facebook.github.io/react/docs/getting-started.html)
- [webpack module bundler](https://webpack.github.io/)
- [ESLint - Pluggable JavaScript linter](http://eslint.org/)

## 最近やったReactの学習

- [React.jsを学んでみたメモ – Morizotter Blog](http://blog.morizotter.com/2016/01/10/my-first-react-js/)
- [TypeScript-ReactのHelloWorldメモ – Morizotter Blog](http://blog.morizotter.com/2016/01/10/front-development-environment/)
- [はぁはぁ、、、ReactをTypeScriptで書いてみたはぁはぁ… – Morizotter Blog](http://blog.morizotter.com/2016/01/10/react-with-typescript/)

## 参考にした記事

ありがとうございました！

- [JavaScript - webpack+babel環境でフロントエンドもES6開発 - Qiita](http://qiita.com/HayneRyo/items/74892d3a37ee96a5df60)
- [時代はESLint。JSLintでもJSHintでもなくESLint。 - Qiita](http://qiita.com/inuscript/items/dcf48f56d8f484c0a1a8)
- [JSX - ESLintについての細かいあれこれ - Qiita](http://qiita.com/inuscript/items/8610d3806a3f94d7d1d0)
- [JavaScript - ESLint 最初の一歩 - Qiita](http://qiita.com/mysticatea/items/f523dab04a25f617c87d)
- [reactjs - ES6版React.jsチュートリアル - Qiita](http://qiita.com/nownabe/items/2d8b92d95186c3941de0)

## 終わりに

とりあえず、ほんとうに基本的な部分はやったのですが、繰り返してやって雰囲気つかめてきました。あとは、[Awesome]([enaqx/awesome-react](https://github.com/enaqx/awesome-react))でも見ながら慣れていこうと思います。とりあえず、[Radium](http://stack.formidable.com/radium/)使ってみたい。