RESTfulAPIのFrontWebをReact.jsで構築する準備


[RESTfulAPIのFrontWebをAngular.jsで書こうかと思った](http://qiita.com/susieyy/items/8e860d4020d13eb8baeb)のですが、FRPパラダイムのReact.jsが面白そうなのでこちらで構築してみます。


## 環境

+ node.js v0.11.14
+ npm v2.1.11
+ bower v1.3.12
+ gulp v3.8.8
+ Yeoman v1.3.3
+ React.js latest

## インストール

nodeとnpmをインストールします。

```Shell
$ brew install node
$ brew install npm
```

npmで以下ライブラリをインストールします。

```Shell
$ npm install -g yo bower gulp generator-react-gulp-browserify
```

## ジェネレート

Yeomanで雛形プロジェクトをジェネレートします。
以下の項目を個別に設定できます。

```
❯◉ Sass with Compass
 ◉ Bootstrap
 ◉ Modernizr
 ◉ HTML template - Jade
 ◉ CoffeeScript for JavaScript
 ◉ Jest for unit tests
```

```Shell
$ mkdir Test
$ cd Test
$ yo react-gulp-browserify

     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___\   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

You're using the fantastic React generator. We provide you full JavaScript solution with Sass support!
? What is this project's name? Test
❯◉ Sass with Compass
 ◉ Bootstrap
 ◉ Modernizr
 ◉ HTML template - Jade
 ◉ CoffeeScript for JavaScript
 ◉ Jest for unit tests
? What more would you like? Sass with Compass, Bootstrap, Modernizr, HTML template - Jade, CoffeeScript for JavaScript, Jest for unit tests
   create package.json
   create gulpfile.js
   create bower.json
   create app/styles/main.scss
   create app/index.html
   create app/scripts/app.coffee
   create app/scripts/ui/Timer.coffee
   create app/scripts/ui/__tests__/Timer-test.js
   create app/favicon.ico
   create .bowerrc
   create .gitignore
   create app/robots.txt
   create .editorconfig
   create .jshintrc


I'm all done. Running bower install & npm install for you to install the required dependencies. If this fails, try running the command yourself.


npm WARN package.json test@0.0.0 No description
npm WARN package.json test@0.0.0 No repository field.
npm WARN package.json test@0.0.0 No README data
bower not-cached    git://github.com/Modernizr/Modernizr.git#^2.8.3
bower resolve       git://github.com/Modernizr/Modernizr.git#^2.8.3
bower cached        git://github.com/twbs/bootstrap-sass.git#3.3.1
bower validate      3.3.1 against git://github.com/twbs/bootstrap-sass.git#>=3.3.0
bower cached        git://github.com/jquery/jquery.git#2.1.1
bower validate      2.1.1 against git://github.com/jquery/jquery.git#~2.1.1
bower cached        git://github.com/jquery/jquery.git#2.1.1
bower validate      2.1.1 against git://github.com/jquery/jquery.git#>= 1.9.0
bower download      https://github.com/Modernizr/Modernizr/archive/v2.8.3.tar.gz
bower extract       modernizr#^2.8.3 archive.tar.gz
bower invalid-meta  modernizr is missing "main" entry in bower.json
bower invalid-meta  modernizr is missing "ignore" entry in bower.json
bower resolved      git://github.com/Modernizr/Modernizr.git#2.8.3
npm WARN engine jest-cli@0.1.18: wanted: {"node":"0.8.x || 0.10.x"} (current: {"node":"0.11.14","npm":"2.1.11"})

> v8flags@1.0.8 install /Users/susieyy/tmp/yo/node_modules/gulp/node_modules/v8flags
> node fetch.js

bower new           version for git://github.com/jquery/jquery.git#~2.1.1
bower resolve       git://github.com/jquery/jquery.git#~2.1.1
bower new           version for git://github.com/jquery/jquery.git#>= 1.9.0
bower resolve       git://github.com/jquery/jquery.git#>= 1.9.0
bower download      https://github.com/jquery/jquery/archive/2.1.3.tar.gz
bower download      https://github.com/jquery/jquery/archive/2.1.3.tar.gz
bower extract       jquery#~2.1.1 archive.tar.gz
bower extract       jquery#>= 1.9.0 archive.tar.gz
bower resolved      git://github.com/jquery/jquery.git#2.1.3
bower resolved      git://github.com/jquery/jquery.git#2.1.3
bower install       bootstrap-sass-official#3.3.1
bower install       modernizr#2.8.3
bower install       jquery#2.1.3
```

## 開発用RESTFulAPIサーバへのProxy設定

`/api`というPrefixでパスにアクセスした場合に、localhostの3000ポートにプロキシする設定を行います。
gulpでプロキシを行うために、以下のパッケージをインストールします。

```Shell
$ npm install --save-dev gulp-connect
$ npm install --save-dev proxy-middleware
```

続いて以下を`gulpfile.js`に追記します。

```gulpfile.js

var connect = require('gulp-connect');
var proxy = require('proxy-middleware');
var url = require('url');

gulp.task('connect', function() {
  connect.server({
    root: ['dist'],
    port: 9000,
    livereload: true,
    // /apiにきたリクエストは http://localhost:3000/api にプロキシする。
    middleware: function(connect, o) {
        return [ (function() {
            var options = url.parse('http://localhost:3000/api');
            options.route = '/api';
            return proxy(options);
        })() ];
    }
  });
});

gulp.task('default', ['connect']);
```

## サーバ起動

localhostの9000ポートでアクセスできるようになります。


```Shell
$ gulp watch
```

![DevSpot_Front.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/80dc3e7d-2f99-7282-ca83-52db73b46ccc.jpeg "DevSpot_Front.jpg")

## コード

メインのスクリプトはこんな感じです。
React.jsが宣言的なので、簡素に表記するCoffeeScriptとも相性がよさそうです。

```app/scripts/app.coffee
###*
@jsx React.DOM
###
React = window.React = require("react")
Timer = require("./ui/Timer.coffee")
mountNode = document.getElementById("app")
TodoList = React.createClass(
  displayName: "TodoList"
  render: ->
    createItem = (itemText) ->
      React.createElement "li", null, itemText

    React.createElement "ul", null, @props.items.map(createItem)
)
TodoApp = React.createClass(
  displayName: "TodoApp"
  getInitialState: ->
    items: []
    text: ""

  onChange: (e) ->
    @setState text: e.target.value
    return

  handleSubmit: (e) ->
    e.preventDefault()
    nextItems = @state.items.concat([@state.text])
    nextText = ""
    @setState
      items: nextItems
      text: nextText

    return

  render: ->
    React.createElement "div", null, React.createElement(TodoList,
      items: @state.items
    ), React.createElement("form",
      onSubmit: @handleSubmit
    , React.createElement("input",
      onChange: @onChange
      value: @state.text
    ), React.createElement("button", null, "Add #" + (@state.items.length + 1))), React.createElement(Timer, null)
)
React.renderComponent React.createElement(TodoApp, null), mountNode
```

Javascriptの場合はこんな感じです。

```app/scripts/app.js
/** @jsx React.DOM */

var React = window.React = require('react'),
    Timer = require("./ui/Timer"),
    mountNode = document.getElementById("app");

var TodoList = React.createClass({
  render: function() {
    var createItem = function(itemText) {
      return <li>{itemText}</li>;
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [], text: ''};
  },
  onChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems, text: nextText});
  },
  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
        <Timer />
      </div>
    );
  }
});


React.renderComponent(<TodoApp />, mountNode);
```



## REF

+ [[JavaScript] 仮想DOMを提供する『React』について少し調べてみた](http://qiita.com/hkusu/items/ee5bc75c3c32185c7b76)
+ [React.jsについてのリソース、そして締め](http://qiita.com/koba04/items/9df76ac0744edd8f8e0e)
+ [一人React.js Advent Calendar 2014](http://qiita.com/advent-calendar/2014/reactjs)