ExpressでReactを始める

# はじめに

調べ方が悪かったのか、それとも調べなくてもわかるのが当たり前の世界なのかは不明だけど、Expressをベースにしてて、且つ自分が知りたい部分のReactの解説をしてるような情報が見つからなかったので残しておく。

## やりたかったこと

シンプルに言えば、「ExpressのViewエンジンをReactにする」。ただそれだけ。
それだけのはずなんだけど、ピンポイント過ぎたのか思うような情報は得られなかった。

元々EJSを使っててReactに乗り換えてみようと思ったら、これが原因でいきなり行き詰まった。
んで、調べた結果なんとかなったので残しておこうかと。

ちなみに、今回は単純にReactで画面表示するとこまでできればそれでいいという内容です。
"server-side rendering"はしないし、ルーティングもExpressのRouterを利用するだけでSPAにはなってません。てゆーか画面複数作りません。
内容は薄いので、あんまり期待しないでください。

# 前提

あとで _package.json_ の中身を載せるので、そこでも諸々確認できると思うけど根本的なとこを先に。

ちなみに、Node開発自体初心者じゃないことは大前提。
そこは考慮してないので、初心者だとわけわからないところもあると思います。

## IDE

Cloud9を使う。
基本、Nodeの開発する時はいつもCloud9使ってるんで、Cloud9上の話だという前提で読まないとつまるとこがあるかも。

## Nodeのバージョン

4.x系を使ってるので、0.10.x・0.11.x・0.12.xとかiojsとか、統合が決まる前のゴニョゴニョしてる時代のバージョンは考慮してない。
Node自体の情報には詳しくないので、後方互換なのかとかその辺の事情も知らないです。
そーゆーのが気になる人は、英語の記事読みあさってください。

## npm

2.x系使ってます。
3.x系でも問題ないとは思ってるけど一応。
あと、インストールでエラーが出るような事象が発生した場合は[こっちの記事](http://qiita.com/asma/items/406cc3c65b83d509d76c)を参考にしてみてください。解消するかもしれないです。

# 環境構築

## プロジェクト作成

### プロジェクト作成の前に

Express使ってる人は知ってるかもしれないけど、コマンドラインから簡単にプロジェクト生成ができるんで、先に _express-generator_ をインストールしておく。

```bash
npm i -g express-generator
```

上記コマンドを実行して、インストールが完了したら以下のコマンドで確認。

```bash
express -h
```

ヘルプが表示されればOK。

```bash
  Usage: express [options] [dir]

  Options:

    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css <engine>  add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory
```

### プロジェクトを作成する

_express-generator_ を利用して、コマンドラインからプロジェクト生成。

```bash
express ./ReactSample
```

一瞬でプロジェクトの生成が完了。
以下のようなメッセージが表示される。

```bash
   create : ./ReactSample
   create : ./ReactSample/package.json
   create : ./ReactSample/app.js
   create : ./ReactSample/public
   create : ./ReactSample/public/javascripts
   create : ./ReactSample/routes
   create : ./ReactSample/routes/index.js
   create : ./ReactSample/routes/users.js
   create : ./ReactSample/public/stylesheets
   create : ./ReactSample/public/stylesheets/style.css
   create : ./ReactSample/views
   create : ./ReactSample/views/index.jade
   create : ./ReactSample/views/layout.jade
   create : ./ReactSample/views/error.jade
   create : ./ReactSample/public/images
   create : ./ReactSample/bin
   create : ./ReactSample/bin/www

   install dependencies:
     $ cd ./ReactSample && npm install

   run the app:
     $ DEBUG=ReactSample:* npm start
```

どんなディレクトリやファイルが生成されたのかと、このあとはどうすればいいのかのヒントっぽいものが表示される感じ。
これでとりあえずプロジェクトの作成が完了。

## npmで色々インストール

### グローバルなやつ

* **forever**  
Nodeをデーモン化するおなじみのやつ。  
ほかにも色々選択肢はあるんだと思うけど、いつもこれ使ってます。
* **webpack**  
browserify と同じ理由でインストール。

以下、インストールコマンド。

```bash
npm i -g forever webpack
```

### プロジェクト固有なやつ

プロジェクトルートに置かれてる _package.json_ に、必要なモジュールを追記して一気にインストール。
以下の内容に書き換える。まるっとコピペでもOK:+1:

```json
{
  "name": "ReactSample",
  "version": "0.0.0",
  "private": true,
  "engine": {
    "node": "4.x",
    "npm": "2.x"
  },
  "dependencies": {
    "async": "^1.5.0",
    "babel-preset-react": "^6.1.18",
    "babelify": "^7.2.0",
    "body-parser": "^1.14.1",
    "cookie-parser": "^1.4.0",
    "debug": "^2.2.0",
    "express": "^4.13.3",
    "express-react-views": "^0.9.0",
    "react": "^0.14.3",
    "react-bootstrap": "^0.27.3",
    "react-dom": "^0.14.3",
    "serve-favicon": "^2.3.0"
  }
}
```

プロジェクト作成時に自動生成される _package.json_ にはdependenciesが記述されている。それを流用してるからそのまま残してる記述もある。
基本的には、Reactの[公式サイト](https://facebook.github.io/react)にある「**Get Started**」の中に掲載されているものをそのまま追記しているだけ。
今回重要なのはReactの公式サイトには載ってないもので、 **"express-react-views"** ってやつ。
名前そのままなんだけど、こいつを使えばReactをExpressのViewエンジンとして利用できるようになる。

_package.json_ の修正が終わったら、プロジェクトルートで `npm i` する。

# Hello world

## 自動生成されたファイルの修正

自動生成されたファイルのソースコードを一部修正する。

```diff:app.js
@@ -1,29 +1,26 @@
-var express = require('express');
-var path = require('path');
-var favicon = require('serve-favicon');
-var logger = require('morgan');
+var express      = require('express');
+var path         = require('path');
+var favicon      = require('serve-favicon');
 var cookieParser = require('cookie-parser');
-var bodyParser = require('body-parser');
-
-var routes = require('./routes/index');
-var users = require('./routes/users');
+var bodyParser   = require('body-parser');
+var routes       = require('./routes');

 var app = express();

 // view engine setup
 app.set('views', path.join(__dirname, 'views'));
-app.set('view engine', 'jade');
+app.set('view engine', 'jsx');
+app.engine('jsx', require('express-react-views').createEngine());

 // uncomment after placing your favicon in /public
-//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
-app.use(logger('dev'));
+app.use(favicon(path.join(__dirname, 'public/img', 'asma_icon_32x32.ico')));
 app.use(bodyParser.json());
 app.use(bodyParser.urlencoded({ extended: false }));
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));

-app.use('/', routes);
-app.use('/users', users);
+// routing
+require('./routes/route_bind')(app, routes);

 // catch 404 and forward to error handler
 app.use(function(req, res, next) {
```

下記の箇所でViewエンジンとしてReactを使用するように設定している。
あとはfaviconを指定したりルーティングを変更したりしてる感じ。

```js
app.set('view engine', 'jsx');
app.engine('jsx', require('express-react-views').createEngine());
```

ルーティングの実装を変更するために下記のような内容に変更。

```diff:routes/index.js
@@ -1,9 +1,13 @@
-var express = require('express');
-var router = express.Router();
+/**
+ * routes list
+ *
+ * @package Route
+ * @author asma
+ * @copyright 2015 asma All Rights Reserved.
+ */
 
-/* GET home page. */
-router.get('/', function(req, res, next) {
-  res.render('index', { title: 'Express' });
-});
+var routes = {
+  demo: require('../controller/demo')
+};
 
-module.exports = router;
+module.exports = routes;
```

自動生成されるViewファイルはJadeで記述されていたのでJSXに変更する。
"views/error.jade"を"views/error.jsx"に変更して以下のような内容に修正。

```js
var React = require('react');

var errPage = React.createClass({
  render: function () {
    return (
      <div className="contents">
        <h1>{this.props.message}</h1>
        <h2>{this.props.error.status}</h2>
        <pre>{this.props.error.stack}</pre>
      </div>
    );
  }
});

module.exports = errPage;
```

## 新規に作ったファイル

### ルーティング関連

自動生成された記述のままだと、ルーティングが増える度に"app.js"に行を追加しなければならないような感じになっているので、"routes/index.js"と併せて別ファイルでルーティングを管理しつつコントローラーっぽいものを作ってみた。

```js:routes/route_bind.js
module.exports = function (app, routes) {
  app.use('/', routes.demo);
};
```

```js:controller/demo.js
var express = require('express');
var router  = express.Router();

router.get('/', function (req, res, next) {
  res.render('demo/index', {});
});

module.exports = router;
```

### JSX

ViewをJSXで実装する。

```js:views/demo/index.jsx
var React = require('react');
var ReactBS = require('react-bootstrap');

var Content = React.createClass({
  getDefaultProps: function () {
    return {
      title: 'ReactSample',
      outputWord: 'Hello World!!'
    };
  },
  render: function () {
    return (
      <html lang="ja">
      <head>
        <meta charSet="UTF-8" />
        <title>{this.props.title}</title>
        <meta name="author" content="asma" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />

        {/* styles */}
        <link type="text/css" rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
        <link type="text/css" rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" />
        <link type="text/css" rel="stylesheet" href="/css/demo.css" />
        {/* /styles */}

        {/* scripts */}
        <script charSet="UTF-8" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
        <script charSet="UTF-8" src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
        {/* /scripts */}
      </head>
      <body>
        {/* header */}
        <ReactBS.ButtonGroup justified>
          <ReactBS.Button href="#">ボタン01</ReactBS.Button>
          <ReactBS.Button href="#">ボタン02</ReactBS.Button>
          <ReactBS.DropdownButton title="ドロップダウン" id="bg-justified-dropdown" data-toggle="dropdown">
            <ReactBS.MenuItem eventKey="1">リンク01</ReactBS.MenuItem>
            <ReactBS.MenuItem eventKey="2">リンク02</ReactBS.MenuItem>
          </ReactBS.DropdownButton>
        </ReactBS.ButtonGroup>
        {/* /header */}
        {/* container */}
        <div className="container-fluid">
          <h1 className="helloWorld">{this.props.outputWord}</h1>
        </div>
        {/* /container */}
      </body>
      </html>
    );
  }
});

module.exports = Content;
```

## その他

### ディレクトリ名の変更

自動生成されたディレクトリ名のままだとなんかちょっとな。。。というのを見慣れた感じに変更した。

```
public/images      → img
public/javascripts → js
public/stylesheets → css
```

### 削除したファイル

不要なので削除したファイルは以下。

```
routes/users.js
views/layout.jade
```

## 画面表示

実際に表示される画面はこんな感じ。

![sampleView.png](https://qiita-image-store.s3.amazonaws.com/0/38966/26357740-5c03-66e9-eb7c-13d30e95acdf.png)

# 最後に

**"express-react-views"** を利用することで、思っていたよりもだいぶ簡単に「Express+React」を実現することができた。
今度は"server-side rendering"とかSPAを試してみようかなと思います。

## GitHub

今回やってみた内容をGitHubに公開してあるので参考にどうぞ。

https://github.com/asma-dev/ReactSample