Reduxについて勉強した #1


本記事はReduxのチュートリアルであり、Reactについての詳しい説明はここでは扱わない。

基本的には[こちら](https://egghead.io/lessons/javascript-redux-the-single-immutable-state-tree)のビデオチュートリアルをベースに書いている。英語に問題がなく、ビデオが嫌いでなければこちらを参照した方が正直わかりやすいかと思う。

## 下準備

gitと[Node](https://nodejs.org/ja/)はインストール済みであるとする。まずはターミナルで[スターターキット](https://github.com/alicoding/react-webpack-babel)をダウンロードする。このスターターキットにはReact、Webpack、babelが含まれている（Reduxは含まれていない）。

```bash
$ git clone https://github.com/alicoding/react-webpack-babel react-counter
$ cd react-counter
$ npm install
$ npm run start
```

[http://localhost:8888/](http://localhost:8888/)にアクセスして正しく表示されれば成功。ターミナルで``Ctrl + C``を押して一度キャンセルする。

## Reduxとは

Reduxは、アプリの状態を管理するためのシンプルなライブラリである。ここで、いくつか用語が出てくる。まずReduxでは、アプリの状態（ **state** ）と、状態を変更するためのアクション（ **action** ）をそれぞれ定義する。

これらは、例えばこれから作るカウントアプリでは以下のようになる。

**state**: アプリは現在カウンターの数値がいくつなのか、という状態。
**action**: 「+1をする」「-1をする」が、それぞれアクション

つまりReduxでは、stateに対してactionを実行して新しいstateにしていく。これを行う関数を **reducer** と呼ぶ。また、アプリは状態(state)を保持する変数を１つだけ持ち、その変数を **store** と呼ぶ。

すなわちアプリの状態（ **state** ）は全て **store** によって保持され、 **reducer** の関数を通して **action** が実行されることによってのみ変更される。そして状態が変更された際に再描画をすることで常に最新の状態がユーザーから見えるようになる。図にすると以下のようになる。

![redux-image.png](https://antelos.qiita.com/files/31aab980-46f9-77fc-6997-08fff8b27057.png "redux-image.png")

state, action, store, reducerは以後よく出てくるので、ここで理解しておきたい。

## なぜReduxを使うのか？

Reactで考えるとわかりやすく、次のようなメリットがある。

* Reactのコンポーネント階層が深くなった時のバケツリレーを回避
* デバッグやテストの自動化が簡単になる

ちなみにReduxはReactに限らず他のJavascriptライブラリでも使うことができる。

[Reduxは必ず使わなければいけないものではない](http://mae.chab.in/archives/2937)

## Reduxのインストール

``npm``を使ってインストールする。

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

## カウントアプリの作成

まずはReactを使わず、Reduxのみでカウントアプリを作成する。``src/index.jsx``を次のように書き換える。

```src/index.jsx
import {createStore} from 'redux';

const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT': 
      return state + 1;
    case 'DECREMENT': 
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counter);

const render = () => {
  document.body.innerText = store.getState();
}

store.subscribe(render);
render();

document.addEventListener('click', () => {
  store.dispatch({ type: 'INCREMENT' });
});
```

``npm run start``を実行して[http://localhost:8888/](http://localhost:8888/)にアクセスする。ページ内のどこかをクリックした時に数値が増えていけば正しく動作している。

このコードについて正しく理解しよう。まず最初の行はReduxのライブラリから``createStore``という関数を使えるよう、インポートしている。

次に``counter``という関数を作っている。この関数が、前述したreducerになっている。引数として``state``と``action``を受け取り、それらに応じて新しい``state``を返り値として返却する。ここでは、``action.type``が'INCREMENT'のときはstateを+1し、'DECREMENT'のときはstateを-1している。

このreducerを元に、Reduxによって``store``という変数を作っている。名前の通りstoreとなる。

そして、stateが更新された際の処理として``render``関数を定義し、``subscribe``している。``render``関数では単にページに最新のstate（現在のカウント）を表示している。

最後に、ページ内がクリックされた際、storeに対してactionを発行（``dispatch``）している。コードの通り、actionは``type``要素を持つオブジェクトである。

## createStoreの実装はどのようになっているのか？

Reduxの``createStore``の中では何が行われているのだろうか。面白いことに、実装は以下のように非常にシンプルである（実際には例外処理など、もう少しだけ長いコードになっている）。

```js
const createStore = (reducer) => {
  let state;
  let listeners = [];
  
  const getState = () => state;
  
  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };
  
  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener)
    }
  };
  
  dispatch({});
  
  return { getState, dispatch, subscribe };
}
```

コードの通り、storeは``state``と、``listeners``（状態の変更を監視しているイベントリスナー達）を持つ。また、現在のstateを取得する``getState``、そしてactionを発行する``dispatch``、状態の変更を監視するための``subscribe``という3つの関数を持つ。``state``や``listeners``は外部から直接変更することができないようになっている。

## Reactを使って描画する

カウントアプリをReactで描画するように変更する。コードはほとんど変わらない。まず``src/app.jsx``を以下のように変更し、数字を表示する見出しと、「+1」と「-1」の２つのボタンを置く。

```src/app.jsx
import 'bootstrap/dist/css/bootstrap.min.css';
import React from 'react';

export default class App extends React.Component {
  render() {
    return (
      <div>
        <h1>{this.props.value}</h1>
        <button className='btn btn-primary' onClick={this.props.onIncrement}>+</button>
        <button className='btn btn-danger' onClick={this.props.onDecrement}>-</button>
      </div>
    )
  }
}
```

``src/index.jsx``の方ではReactDOMによってこれを描画する。全体のコードは以下のようになる。

```src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import App from './app.jsx';

const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counter);

const render = () => {
  ReactDOM.render(
    <App
      value={store.getState()}
      onIncrement={ () =>
        store.dispatch({
          type: 'INCREMENT'
        })
      }
      onDecrement={ () =>
        store.dispatch({
          type: 'DECREMENT'
        })
      }
    />,
    document.getElementById('app')
  )
}

store.subscribe(render);
render();
```

プラスボタンとマイナスボタンのあるカウントアプリが完成した！