React TutorialをES2015で書いた時のメモ


[React Tutorial](https://facebook.github.io/react/docs/tutorial.html)をES2015で書いた時のメモ。

コンポーネントの構造は以下。

```
- CommentBox
  - CommentList
    - Comment
  - CommentForm
```

## 準備
いろいろと`npm install`した結果の`package.json`。

```json:package.json
{
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "babel-cli": "^6.9.0",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "webpack": "^1.13.1"
  },
  "dependencies": {
    "marked": "^0.3.5",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "superagent": "^1.8.3"
  }
}
```

[webpack](https://webpack.github.io/)でビルドするので、そちらの設定も。

```js:webpack.config.babel.js
import path from 'path';

const src = path.resolve(__dirname, 'src');
const dist = path.resolve(__dirname, 'dist');

export default {
  entry: `${src}/app.js`,

  output: {
    path: dist,
    filename: 'bundle.js'
  },

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        include: [src],
        loader: 'babel'
      }
    ]
  }
};
```

## ES2015 + React で記述
jsを読み込むHTMLを用意。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Tutorial</title>
  </head>
  <body>
    <div id="container"></div>
    <script src="./dist/bundle.js"></script>
  </body>
</html>
```

一番上のレイヤーのCommentBoxコンポーネントを`render`するjs。Webpackの設定にも書いたように、このファイルをトランスパイルすることになる。

```js:src/app.js
import React from 'react';
import { render } from 'react-dom';
import CommentBox from './components/CommentBox';

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

各コンポーネントの記述。
class記法を使ってコンポーネントを作成。

```js:src/components/CommentBox.js
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);
    this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
  }
  loadCommentsFromServer () {
    request
      .get(this.props.url)
      .end((error, response) =>
        this.setState({data: JSON.parse(response.text)}));
  }
  handleCommentSubmit(comment) {
    const comments = this.state.data;
    comment.id = Date.now();
    const newComments = [...comments, comment];
    this.setState({data: newComments});

    request
      .post(this.props.url)
      .send(comment)
      .set('Content-Type', 'application/x-www-form-urlencoded')
      .end((error, response) => {
        if (error !== null) {
          this.setState({data: comments});
        } else {
          this.setState({data: JSON.parse(response.text)});
        }
      });
  }
  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} />
      </div>
    );
  }
}
```
```js:src/components/CommentList.js
import React from 'react';
import Comment from './Comment';

export default class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const commentNodes = this.props.data.map((comment) =>
      <Comment author={comment.author} key={comment.id}>{comment.text}</Comment>);
    return (
      <div className="commentList">{commentNodes}</div>
    );
  }
}
```
```js:src/components/Comment.js
import React from 'react';
import marked from 'marked';

export default class Comment extends React.Component {
  constructor(props) {
    super(props);
  }
  rawMarkup() {
    const 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:src/components/CommentForm.js
import React from 'react';

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

    this.handleAuthorChange = this.handleAuthorChange.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleAuthorChange(e) {
    this.setState({author: e.target.value});
  }
  handleTextChange(e) {
    this.setState({text: e.target.value});
  }
  handleSubmit(e) {
    e.preventDefault();
    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() {
    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>
    );
  }
}
```

これで終わり。
`npm run build`すると、トランスパイルされた`dist/bundle.js`が生成されるので、それをHTMLで読み込む。

### ※サーバサイド
RubyのSinatraで、非常に雑な実装のやつを書いた。

```rb:Gemfile
gem 'sinatra'
gem 'sinatra-contrib'
```

```rb:app.rb
require 'sinatra'
require 'sinatra/reloader' if development?
require 'json'

response_body = [
  { id: 1,
    author: "Peter Hunt",
    text: "This is one comment"
  },
  {
    id: 2,
    author: "Jordan Walke",
    text: "This is *another* comment"
  }
]

get '/api/comments' do
  headers 'Access-Control-Allow-Origin' => '*'
  body response_body.to_json
end

post '/api/comments' do
  response_body << {
    id: response_body.last[:id] + 1,
    author: params["author"],
    text: params["text"]
  }

  headers 'Access-Control-Allow-Origin' => '*'
  body response_body.to_json
end
```

`ruby app.rb`を実行すると`http://localhost:4567`で開発サーバが起動し、APIにアクセス可能になる。