React Tutorial Example (Redux)

[React](https://facebook.github.io/react/) は [Flux](http://facebook.github.io/flux/) のフレームワークで利用すると効果的です。
[Redux](http://redux.js.org/) は *Flux* を参考に [3つの原則](http://redux.js.org/docs/introduction/ThreePrinciples.html) を強調したフレームワークです。
[前回](http://qiita.com/ogomr/items/9eb64ff5f7d4fcd08514) のアプリケーションに *Redux* を追加してみましょう。

## Learning

* Redux の *Action, Reducer, Store* の *Data Flow* を利用する。
* React と Redux で連携 *connect* を利用する。

## Environment

* node: v4.4.5
* npm: v3.9.6

## Comment Box Form

* 完成される [Source Code](https://github.com/ogom/react-comment-box-example/tree/redux) のファイルリストです。

```bash
$ tree -a -I node_modules
.
├── .babelrc
├── app.js
├── index.html
├── index.js
├── package.json
├── style.css
└── webpack.config.js
```

*Let's hands-on*

### Setup application

* `git clone` コマンドでアプリケーションをダウンロードします。
* `npm install` コマンドで依存するモジュールをインストールします。

```bash
$ git clone https://github.com/ogom/react-comment-box-example.git
$ cd react-comment-box-example
$ git checkout react
$ npm install
```

### Start HTTP Server

* `npm start` コマンドで *Webアプリケーション* を実行します。
* ブラウザで http://localhost:4000 をロードすると **Comment Box Example** が表示されます。

```bash
$ npm start
$ open http://localhost:4000
```

(API Server は [React Tutorial Example (Express)](http://qiita.com/ogomr/items/b90c68764c3c75cf501e) をご利用ください。)

### Add Redux package

* Redux のモジュールをインストールします。

```bash
$ npm install redux --save-dev
```

### Add Redux

* `createStore()` で *Reducer* を設定して *Store* を作成します。
  * `store.dispatch()` で *Acstion* を実行すると *State* が変更されます。

```diff:index.js
+var Redux = require('redux');
+var createStore = Redux.createStore;
+var store = createStore(function(state, action) {
+  if (state === undefined) {
+    return [];
+  }
+
+  switch (action.type) {
+  case 'show_comments':
+    return action.comments;
+  case 'add_comment':
+    return state.concat([action.comment]);
+  default:
+    return state;
+  }
+});
+store.subscribe(function () {
+  return console.log(store.getState());
+});
+store.dispatch({type: 'show_comments', comments: data});
+store.dispatch({type: 'add_comment', comment: {author: "foo", text: "*bar*"}});
```

* `store.getState()` を `console.log` で出力すると *State* を確認することができます。

![redux_comment](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/node/comment-box/redux_comment.png)

### Add React Redux package

* ReactRedux のモジュールをインストールします。

```bash
$ npm install react-redux --save-dev
```

### Add Provider

* `Provider` に `store` を設定して *React* と *Redux* を連携します。

```diff:index.js
-var data = [
-  {id: 1, author: "Pete Hunt", text: "This is one comment"},
-  {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
-];
-
-store.subscribe(function () {
-  return console.log(store.getState());
-});
-store.dispatch({type: 'show_comments', comments: data});
-store.dispatch({type: 'add_comment', comment: {author: "foo", text: "*bar*"}});
+
+var ReactRedux = require('react-redux');
+var Provider = ReactRedux.Provider;

 ReactDOM.render(
-  <CommentBox url="http://localhost:3000/api/comments" pollInterval={2000} />,
+  <Provider store={store}>
+    <CommentBox url="http://localhost:3000/api/comments" pollInterval={2000} />
+  </Provider>,
   document.getElementById('content')
 );
```

### Add Connect

* `mapStateToProps` で `state` をマッピングします。
* `mapDispatchToProps` で `dispatch` をマッピングします。
* `connect()` でマッピングとコンポーネントを接続します。

```diff:app.js
-module.exports = CommentBox;
+var ReactRedux = require('react-redux');
+var connect = ReactRedux.connect;
+
+var mapStateToProps = function(state) {
+  return {data: state};
+};
+
+var mapDispatchToProps = function(dispatch) {
+  return {
+    showComments: function(comments) {
+      dispatch({type: 'show_comments', comments: comments});
+    },
+    addComment: function(comment) {
+      dispatch({type: 'add_comment', comment: comment});
+    }
+  };
+};
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(CommentBox);
```

### Change Show Comments

* `success` の `this.setState` を `this.props.showComments` に置き換えます。
* `render` の `this.state.data` を `this.props.data` に置き換えます。

```diff:app.js
 var CommentBox = React.createClass({
   loadCommentsFromServer: function() {
     $.ajax({
       url: this.props.url,
       dataType: 'json',
       cache: false,
       success: function(data) {
-        this.setState({data: data});
+        this.props.showComments(data);
       }.bind(this),
       error: function(xhr, status, err) {
         console.error(this.props.url, status, err.toString());
       }.bind(this)
     });
   },
   render: function() {
     return (
       <div className="commentBox">
         <h1>Comments</h1>
-        <CommentList data={this.state.data} />
+        <CommentList data={this.props.data} />
         <CommentForm onCommentSubmit={this.handleCommentSubmit} />
       </div>
     );
   }
 });
```

### Change Add Comment

* `newComments` の `this.setState` を `this.props.addComment` に置き換えます。
* `success` の `this.setState` を `this.props.showComments` に置き換えます。
* `error` の `this.setState` も `this.props.showComments` に置き換えます。

```diff:app.js
 var CommentBox = React.createClass({
     // id generated by the server. In a production application you would likely
     // not use Date.now() for this and would have a more robust system in place.
     comment.id = Date.now();
-    var newComments = comments.concat([comment]);
-    this.setState({data: newComments});
+    this.props.addComment(comment);
     $.ajax({
       url: this.props.url,
       dataType: 'json',
       type: 'POST',
       data: comment,
       success: function(data) {
-        this.setState({data: data});
+        this.props.showComments(data);
       }.bind(this),
       error: function(xhr, status, err) {
-        this.setState({data: comments});
+        this.props.showComments(comments);
         console.error(this.props.url, status, err.toString());
       }.bind(this)
     });
```

*Congrats!*

## Redux Source

Redux は *Data Flow* を **Action**, **Reducer**, **Store** に責務を分離するパラダイムです。
`createStore` の Source code を読むと、だいたいの流れがイメージできます。

### Example

Redux は React がなくても単独で実行することができます。
[JS Bin](http://jsbin.com) で動作するサンプルを作成しました。

#### HTML

* `cdnjs.cloudflare.com` の `redux` を参照します。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>JS Bin</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.min.js"></script>
  </head>
  <body>
  </body>
</html>
```

#### JavaScrit

* `var store = createStore()` の 引数が `reducer` です。
  * https://github.com/reactjs/redux/blob/v3.5.2/src/createStore.js#L39

* `store.dispatch()` の 引数が `action` です。
  * https://github.com/reactjs/redux/blob/v3.5.2/src/createStore.js#L149

```js
var createStore = Redux.createStore;
var store = createStore(function(state, action) {
  if (state === undefined) {
    return 0;
  }

  switch (action.type) {
  case 'PLUS':
    return state + action.number;
  case 'MINUS':
    return state - action.number;
  default:
    return state;
  }
});

store.subscribe(function () {
  return console.log(store.getState());
});

store.dispatch({type: 'PLUS', number: 2});
store.dispatch({type: 'PLUS', number: 3});
store.dispatch({type: 'MINUS', number: 4});
store.dispatch({type: 'DEFAULT'});
```

#### Console

* 数値が増加するアクションや減少するアクションで **State** が更新されています。

![redux_number](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/node/comment-box/redux_number.png)

#### ECMAScript 2015

ECMAScript 2015 ならもっと簡潔に記述することができます。

```js
import {createStore} from 'redux'
const store = createStore((state = 1, action) => action.type === 'PLUS' ? state + 1 : state - 1)
store.subscribe(() => console.log(store.getState()))
store.dispatch({type: 'PLUS'})
```

----

*Data Flow* を **Action**, **Reducer**, **Store** に分離できました。
次は [React tutorial で ECMAScript 2015](http://qiita.com/ogomr/items/3f0d7a35d5d0184db019) を使用しましょう！