railsでreact(react-rails)とreduxを動かす


流行りのfluxフレームワークreduxとreact-railsを試してみる。
reduxのexamplesにある、counterをreact-railsを使って書いてみます。
https://github.com/rackt/redux/tree/master/examples/counter


※以下のバージョンで書いてます。

* rails 4.2.3
* redux 3.0.4
* react-rails 1.4.2
* react 0.14.2

ソースは以下におきました。
ソースは@odk211さんからPR頂いてbabel6を使ったものになってます。
https://github.com/suzan2go/react-rails-redux-sample


# 事前準備

## browserify-railsのインストール
`Gemfile`に以下を追加して`bundle install`しましょう。

```
gem 'browserify-rails'
```

reduxのサンプルはES6の書き方を沢山使ってますので、そのまま使うにはトランスパイラが必要になります。`config/application.rb`に以下を追加しましょう

```
config.browserify_rails.commandline_options = '-t babelify'
```

`browserify`と`babelify`、そして`browserify-rails`に必要な`browserify-incremental`をnpmで入れます。

```zsh
npm install --save-dev browserify
npm install --save-dev browserify-incremental
npm install --save-dev babelify
```

### babel6で動かす場合
babelify 7.0 以降を入れる場合など、babel6で動かす場合は以下も必要になるようです

```zsh
npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-react
npm install --save-dev babel-preset-stage-2
```
※ `babel-preset-stage-2`自体はなくても動かせるのですが、今回扱うサンプルの`.babelrc`に`stage-2`の記述があったので、一応追加しています。

railsのアプリケーションrootに、`.babelrc`を追加します。

```json
{
  "presets": ["es2015", "stage-2", "react"]
}
```

## reduxのインストール
今回のexampleでは以下が必要になるので`npm install`します。`1.0.0-rc`より`redux`の中でも`react`に特化した部分が`react-redux`に切りだされています。`react`を使う場合はこちらも必要です。
https://github.com/rackt/redux/releases/tag/v1.0.0-rc


```
npm install --save react
npm install --save redux
npm install --save react-redux
npm install --save redux-thunk
```
`react-rails`でも`react`が入りますが、今回はnpmで入れた`react`を使うことにします。


## react-railsのインストール
なにはともあれ、`react-rails`をインストールします。`Gemfile`に以下を追加して`bundle install`しましょう。

```
gem 'react-rails'
```
インストールが完了したら以下のコマンドを実行します。

```
rails g react:install
```
このコマンドを実行すると以下のファイル、ディレクトリが追加され、`application.js`に記述が追加されます。

```
app/assets/javascripts/components.js
app/assets/javascripts/components/
```

`application.js`は多分こんな感じになってるはず。

```
# application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require react
//= require react_ujs
//= require components
//= require_tree .
```
npmでいれた`react`を使うので、`//=require react`は消しても大丈夫です。`//= require_tree .`は不要なので消しておきましょう。

# Reduxのexamplesにあるcounterをreact-railsで動かす
前置きが長くなりましたが、いよいよreact-railsでreduxを動かしてみます。

## reduxのexamplesを持ってくる
`redux`のレポジトリに入っているexaplesから以下のフォルダを`app/assets/javascripts/components/`配下に格納します。ファイルは他にもありますが、とりあえずこれだけあれば動かせます。

* actions
* components
* containers
* reducers
* store

https://github.com/rackt/redux/tree/master/examples/counter

### babel6で動かす場合
公式のcounterのサンプルと同じディレクトリにある`.babelrc`があると、babel6では動きません。サンプルの`.babelrc`は削除して、前述の通りrailsのアプリケーションrootに`.babelrc`を作成してください。(この`.babelrc`を同じように書き換えても動きます。)

```
{
  "presets": ["es2015", "stage-2", "react"]
}
```

## components.jsを書き換える
`react-rails`をインストールしたときに作成した`components.js`を書き換えます。
`Root.js`に定義されている`Root`がカウンターアプリのコンポーネントなので、これを`react-rails`の`react_component`ヘルパから呼べるようにします。

```js

window.React = require('react');
window.React = require('react-dom');
window.Root  = require('./components/containers/Root.js');
// babel6の場合
// window.Root  = require('./components/containers/Root.js').default;
```

## Viewファイルから呼び出す
Viewファイルに`react_component`ヘルパーを記述してブラウザからアクセスしてみると。

```ruby
<%= react_component('Root') %>
```

こんな感じで表示されるようになるはず！

![Kobito.vSZjlv.png](https://qiita-image-store.s3.amazonaws.com/0/76861/f11db5d4-f564-bcf2-a779-3cab7db16147.png "Kobito.vSZjlv.png")

# react_componentヘルパーに渡したパラメータで初期値をセットできるようにする
react_componentヘルパーに初期値を渡して、サーバサイドレンダリングできるようにしてみます。
## Viewファイルを書き換える
まず先ほど記述したViewファイルを以下の様に書き換えます。素のReactコンポーネントの場合はこれで値を渡すことがますが、今回は`redux`を通しているのでこれだけでは値はセットされません。

```ruby
<%= react_component('Root', {counter: 10}) %>
```

## カウンター値をセットするactionを定義する
`actions/counter.js`に以下を追記します。

```javascript
export const SET_COUNTER = 'SET_COUNTER';

export function setCounter(counter) {
  return {
    type: SET_COUNTER,
    counter: counter
  };
}

```

## カウンター値をセットするstoreを定義する
`reducers/counter.js`に以下を追記します。(`redux`では他のフレームワークで`store`となっているものを`reducer`と呼ぶようです。)

```javascript
// SET_COUNTERを追記
import { INCREMENT_COUNTER, DECREMENT_COUNTER, SET_COUNTER } from '../actions/counter';

export default function counter(state = 0, action) {
  switch (action.type) {
  case INCREMENT_COUNTER:
    return state + 1;
  case DECREMENT_COUNTER:
    return state - 1;
  // SET_COUNTERに対応する処理を追加します。
  case SET_COUNTER:
    return action.counter;
  default:
    return state;
  }
}
```

## componentWillMountからsetCountを呼ぶ
`containers/Root.js`に以下を追記します。サーバサイドレンダリングを行う場合、サーバ側で行う処理はReactコンポーネントのライフサイクルの中でも`componentWillMount`に記述する必要があります。
https://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount

```javascript
import {setCounter} from '../actions/counter'

export default class Root extends Component {
  componentWillMount() {
    store.dispatch(setCounter(this.props.counter));
  }
 // 以下省略
```

これで準備は完了です！もう一度アクセスするとこんなかんじになるはず。
![Kobito.n23TSI.png](https://qiita-image-store.s3.amazonaws.com/0/76861/a2be78df-e2c2-c389-d6e1-9916a1cd1b12.png "Kobito.n23TSI.png")

## サーバサイドレンダリング
`react-rails`を使う大きなメリットですね。ヘルパーに`{prerender: true}`とするだけでサーバサイドでのレンダリングが可能になります。試しにブラウザでjavascriptを無効にしても、最初に表示される画面は同じになるはずです。

```ruby
<%= react_component('Root', {counter: 10}), {prerender: true} %>
```

# 感想・所感
react-railsとfluxについて書いてある情報が少なくて困った。`react_component`経由で値を渡すところとか結構手探りなので、flux的におかしいとかあればご指摘ください…
redux以外も結構そうみたいなんですが普通にES6、なんならES7の記法で当たり前のように書いてあるものが多くて、今のプロジェクトやサービスでは使わないにしてもES6読めるようにしとく必要があるなーと感じました。
reactもfluxについても今回はじめてちゃんと勉強したので、react、flux、ES6全部あんまり良くわかっていないところからのスタートはかなりきつかったです。