React TutorialをES6で書いてみた その2

[ここ](http://qiita.com/koichirola/items/78efc308633179554450)でデータ埋め込みの表示までは試したので、通信含めて最後まで試してみた。

## Directory構造

```bash:directory構造
$ tree
.
├── index.html
├── index.js
├── modules
│   ├── Comment.js
│   ├── CommentBox.js
│   ├── CommentForm.js
│   └── CommentList.js
├── server.js # これが増えただけ
└── webpack.config.js
```

## koajs
* [公式サイト](http://koajs.com/)

ローカルのサーバにはkoajsを使います。expressと迷ったのですが、おしゃれそうだったので。


```bash:npm
# babelコマンドラインのインストール
$ npm install -g babel-cli

$ npm install --save-dev koa koa-router koa-bodyparser koa-static babel-polyfill
```

`koajs`自体は最低限の機能しかありません。`Middleware`を都度使用するような仕組みになっているようです。
今回は、`koa-router`(ルーティングに必要)と`koa-bodyparser`(jsonをパースするのに必要)と`koa-static`(静的ファイルを配信するのに必要)のMiddlewareを使います。
`async`と`await`を内部で使っている？らしく、そのままではbabelでコンパイルできないため、[babel-polyfill](https://babeljs.io/docs/usage/polyfill/)をインストールします。

```js:server.js
import 'babel-polyfill'
import Koa from 'koa'
import Router from 'koa-router'
import bodyparser from 'koa-bodyparser'
import serve from 'koa-static'

const app = new Koa()
const router = new Router()

router
  .get('/api/comments', function* () {
    this.type = 'json'
    this.status = 200
    this.body = [
      { 'author': 'Pete Hunt', 'text': 'This is one comment', 'id': 1 },
      { 'author': 'Jordan Walke', 'text': 'This is *another* comment', 'id': 2 }
    ]
  })
  .post('/api/comments', function* () {
    if (this.is('application/json')
      || this.is('application/x-www-form-urlencoded')) {
      let json = this.request.body
      let author = json['author']
      let text = json['text']

      this.type = 'json'
      this.status = 200
      this.body = [{ 'author': author, 'text': text }]
      this.length = this.body.length
    }
  })

app
  .use(serve('.'))
  .use(bodyparser())
  .use(router.routes())

app.listen(3000);

console.log('listening on port 3000')

```

```bash:サーバ起動
$ babel --presets es2015 server.js -o server.compile.js
$ node server.compile.js
```

JSON返したいだけなのでエラー処理などは省略しています。

## react
React部分の変更点はこちら。
Serverからデータを取得するように変更すると、[keyがユニークではない！](https://facebook.github.io/react/docs/create-fragment.html)と怒られるので、addonをインストールします。

```bash:createFragmentのインストール
$ npm install --save-dev react-addons-create-fragment
```

```js:index.js
import React from 'react'
import { render } from 'react-dom'
import CommentBox from './modules/CommentBox'

// 変更点CommetnBoxをindex.jsでrenderするようにした
render(
  <CommentBox url='/api/comments' pollInterval={2000} />,
  document.getElementById('content')
)

```

```js:modules/CommentBox.js
import React from 'react'
import ReactDOM from 'react-dom'
import CommentList from './CommentList'
import CommentForm from './CommentForm'
import $ from 'jquery'

export default React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: (data) => {
        this.setState({data: data})
      },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString())
      }
    })
  },
  getInitialState: () => {
    return { data: [] }
  },
  componentDidMount: function() {
    this.loadCommentsFromServer()
    setInterval(this.loadCommentsFromServer, this.props.pollInterval)
  },
  handleCommentSubmit: function(comment) {
    let comments = this.state.data
    let newComments = comments.concat([comment])
    this.setState({data: newComments})
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: (data) => {
        this.setState({data: data})
      },
      error: (xhr, status, err) => {
        this.setState({data: comments})
        console.error(this.props.url, status, err.toString())
      }
    })
  },
  render() {
    return (
      <div className='commentBox'>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    )
  }
})

```

```js:modules/CommentList.js
import React from 'react'
import Comment from './Comment'
import createFragment from 'react-addons-create-fragment'

// author passed to author in Comment tag. And child node that is text node passed to children in Comment tag.
export default React.createClass({
  render() {
    // ここでcreateFragmentを使用することでnode間で整合性が保たれる的な？
    const commentNodes = createFragment({
      commentNodes: this.props.data.map((comment) => {
        return (
          <Comment author={comment.author} key={comment.id}>
            {comment.text}
          </Comment>
        )
      })})
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    )
  }
})

```

```js:modules/CommentForm.js
import React from 'react'

export default React.createClass({
  getInitialState: function() {
    return {author: '', text: ''}
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value})
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value})
  },
  handleSubmit: function(e) {
    e.preventDefault()
    let author = this.state.author.trim()
    let text = this.state.text.trim()
    if (!author || !text) {
      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>
    )
  }
})

```

あとは`webpack`をして`localhost:3000`にブラウザでアクセスすれば想定どおりに動作します。

以上でReact Tutorialが終わりました。
後は公式ドキュメントを読み進めながらコード書けば走り出せそうな気がしますね！