ReactJS入門@ES6:Redux編

**対象者：Reactの基本を理解している人 / Reduxについて勉強している人**
前：[ReactJS入門@ES6:ReactRouter編](http://qiita.com/HIGAX/items/e7696fec353203453c0b)
次：未定

***

### まえがき

**Reduxの目的はStateを一元管理することです。**

素のReactでは、Stateはコンポーネントが管理します。
コンポーネントの数が多くなり、親から子、子から子へとStateを継承したり、
コンポーネント間の連携などを考えだすと、Stateをどこかで一元管理し、
コンポーネントはイベントを受け、Stateを変更する命令を出すことに専念したくなります。
その仕組と制約を実現するのがReduxの役目だと、私は考えています。

***

### まずはReduxを使わないカウンターをつくってみる

カウンターをつくりましょう、カウンター。
画面にカウント回数と、プラスボタンとマイナスボタンが表示されているだけのやつです。

```js
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  onIncrement() {
    this.setState({ count: this.state.value + 1 });
  }
  onDecrement() {
    this.setState({ count: this.state.value - 1 });
  }
  render() {
    return (
      <div>
        カウント: {this.state.count} 回
        <br />
        <button onClick={::this.onIncrement}>プラス</button>
        <button onClick={::this.onDecrement}>マイナス</button>
      </div>
    )
  }
}
```

できました。
簡単ですね。

***

### Reduxの前準備

さて、このカウンターをRedux化していきます。
まずRedux化に必須となるActions,Reducer,Storeを作成します。

#### 1. Actions

まずはActionsを考えます。
Actionsとは、**命令コマンド**のことだと思って下さい。
さて、このカウンターではどんな命令コマンドが必要でしょうか。

1. カウントをアップせよ
2. カウントをダウンせよ

まあこの2つ程度ですね。
その命令コマンドをコードに書き起こします。

```js
const INCREMENT_COUNTER = {
  type: 'INCREMENT_COUNTER'
}
const DECREMENT_COUNTER = {
  type: 'DECREMENT_COUNTER'
}
```

終わりです。
簡単ですね。

#### 2. Reducer

次に、Reducerを考えます。
Reducerは、StateとActionsを受け取り、変更したStateを返すだけの処理です。
今回の場合、INCREMENT_COUNTERというActionsを受け取ったらStateのcountをプラスし、
DECREMENTの場合その逆を行うだけの処理を行います。
まあ見れば分かります。

```js
function counterReducer(state = {count: 0}, action) {
  switch (action.type) {
    case 'INCREMENT_COUNTER':
      return {count: state.count + 1}
    case 'DECREMENT_COUNTER':
      return {count: state.count - 1}
    default:
      return state
  }
}
```

簡単ですね。

#### 3. Store

さて、最後にStoreを作成しましょう。
Storeは、Stateを管理するものです。
StateはReducerが変更します。
よって、StoreはReducerから作成します。

```js
const store = Redux.createStore(counterReducer);
```

終わりです。
簡単ですね。

***

### コンポーネント変更の前準備

次にコンポーネントを変更する前準備を行います。

コンポーネントが持つStateはStoreに移動し、
ボタン押下時の処理はReducerに移動しました。
よって、コンポーネントに対し、StateとActionsを割り当てる必要があります。

```js
function mapStateToProps(state) {
  return {
    count: state.count
  };
}

function mapDispatchToProps(dispatch) {
  return {
    onIncrement: () => dispatch(INCREMENT_COUNTER),
    onDecrement: () => dispatch(DECREMENT_COUNTER)
  };
}

let App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);
```

これでcountがコンポーネントのStateとして使用でき、
onIncrementとonDecrementがコンポーネントから呼び出せるようになります。

あとはサクッとコンポーネントを修正します。

```js
class Counter extends React.Component {
  render() {
    const { count, onIncrement, onDecrement } = this.props;
    return (
      <div>
        カウント: {count} 回
        <br />
        <button onClick={onIncrement}>プラス</button>
        <button onClick={onDecrement}>マイナス</button>
      </div>
    )
  }
}
```

最後にReactDOM.renderしたら完成です。

**Providerでラップ、storeの指定、**
**コンポーネントをconnectした結果を指定することに注意。**

```js
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('wrapper')
);
```

***

### 今回作成したサンプルコード

```js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

// Reactのみで構成したカウンター
class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0
    }
  }
  onIncrement() {
    this.setState({ value: this.state.value + 1 });
  }
  onDecrement() {
    this.setState({ value: this.state.value - 1 });
  }
  render() {
    return (
      <div>
        カウント: {this.state.value} 回
        <br />
        <button onClick={::this.onIncrement}>プラス</button>
        <button onClick={::this.onDecrement}>マイナス</button>
      </div>
    )
  }
}

//ReactDOM.render(
//  <ReactCounter />,
//  document.getElementById('root')
//);

/* -- -- -- -- -- ここからRedux用コード -- -- -- -- -- */

// Redux向けに再構成したカウンター
class ReduxCounter extends React.Component {
  render() {
    const { count, onIncrement, onDecrement } = this.props
    return (
      <div>
        {/* ReactCounterも一緒に描画してみる */}
        <ReactCounter />
        カウント: {count} 回
        <br />
        <button onClick={onIncrement}>プラス</button>
        <button onClick={onDecrement}>マイナス</button>
      </div>
    )
  }
}

// Actions
const INCREMENT_COUNTER = {
  type: 'INCREMENT_COUNTER'
};
const DECREMENT_COUNTER = {
  type: 'DECREMENT_COUNTER'
};

// Reducer
function counterReducer(state = {count: 0}, action) {
  switch (action.type) {
    case 'INCREMENT_COUNTER':
      return {count: state.count + 1};
    case 'DECREMENT_COUNTER':
      return {count: state.count - 1};
    default:
      return state
  }
}

// Store
const store = createStore(counterReducer);

function mapStateToProps(state) {
  return {
    count: state.count
  };
}

function mapDispatchToProps(dispatch) {
  return {
    onIncrement: () => dispatch(INCREMENT_COUNTER),
    onDecrement: () => dispatch(DECREMENT_COUNTER)
  };
}

let ReduxCounterApp = connect(
  mapStateToProps,
  mapDispatchToProps
)(ReduxCounter);

// レンダリング
ReactDOM.render(
  <Provider store={store}>
    <ReduxCounterApp />
  </Provider>,
  document.getElementById('root')
);
```