初心者がザックリとReact+Reduxを学ぶ

最近React+Reduxに入門しました。お仕事とは全く関係ありません。興味本位です。

きっかけは『いまから始めるフロントエンド入門』を読んでいて、サンプルコードの実装がまるで理解できなかったためです。

そこでReactとReduxを個別に勉強しました。が、Reactのチュートリアルは何とかこなせたものの、**Reduxの理解に苦しみました。**

ということで自分の備忘録も兼ねて、**本当のリアルガチ初心者向けにReact+Reduxの全体像を恐ろしいほどザックリとまとめてみます。**

なおReactやReduxの設計思想的なものには言及しません。もっときちんと書いてある他の記事を参照してください。また、最低限「React単体のチュートリアルをこなした」程度の人を想定しています。

# 作ったもの
[React tutorial](https://reactjs.org/tutorial/tutorial.html)の**『tictactoe』**（いわゆる◯Xゲーム）をReact+Reduxで実装し直しながら、理解していきました。

<img width="218" alt="2018-02-06_23h20_30.png" src="https://qiita-image-store.s3.amazonaws.com/0/183744/842f93bf-18f2-e324-2a07-bf5de56274ee.png">


作成したコードはGitHubにアップロードしています。

[GitHub - quotto/tictactoe](https://github.com/quotto/tictactoe)

## tictactoeの仕様

- 「X」と「◯」が交互にマスを埋めていきます。
- 3マスに同じマークを並べると勝利でそのラインを赤色で表示します。
- マスを埋めるごとにインデックス付きのボタンを追加します。このボタンを押すことで、その時点のマス状態に戻ることができます。
- 「reverse」をクリックすると、ボタンの並びを逆順にします。

# React+Reduxの全体イメージ
ザックリとこんな感じで理解しています。
![無題のプレゼンテーション (6).png](https://qiita-image-store.s3.amazonaws.com/0/183744/89b0b4f3-3746-1503-1325-0fc699d0c7c9.png)

- Component
- Container
- ActionとActionCreator
- Store
- Reducers

の5つの要素で構成されます。

## tictactoeに当てはめると
ContainerとComponentの関係はこんな感じになります。
![無題のプレゼンテーション (7).png](https://qiita-image-store.s3.amazonaws.com/0/183744/a0b6170c-b62e-d7ee-cedd-70928462c123.png)

今回は各Componentに一つ、ActionCreatorを割当てています。（dispatch）


# 各要素の説明
実際のtictactoeのコードを混じえて、React+Reduxの構成要素について記していきます。

## Component
React Component、つまりView。ユーザからのインプットとそれを受けてのアウトプットを担当します。

何らかのインプットにより更新されたstateを**`props`と言う名前のプロパティで受け取り、画面に反映しています。**

```JavaScript:components/Board.js
class Board extends React.Component {
……
render() {
    return (
      <div>
        {this.props.squares.map((value,i)=>{
          return(
            <div key={"row-"+i} className="board-row">
              {this.props.squares[i].map((value,j)=>{
                return(
                  this.renderSquare(i,j)
                );
              })
              }
            </div>
           );
        })}
      </div>
    );
  }
}
```

## Container
Componentを内包したReactComponent。単独でComponentとContainerの役割を兼ねることもある。

**Containerの役割は大きく2つ。**

- Component上で発生したイベントと実行されるアクションの結び付け。（`mapDispatchToProps`）
- 更新されたstateからpropsを定義し、Componentに引き渡す。（`mapStateToProps`）

### mapDispatchToProps
例えば`BoardContainer`では、`onClick`という名前でActionCreator`putMark`に結び付けている。

```javascript:containers/BoardContainer.js
function mapDispatchToProps(dispatch) {
    return {
        onClick: (i,j) => {
            dispatch(putMark(i,j))
        }
    }
}
```

`onClick`はComponentに`props`として引き渡され、Component内で実際に呼び出す時の引数を設定している。

```javascript:components/Board.js
class Board extends React.Component {
renderSquare(i,j) {
……
return (
      <Square
        key={"square-"+i+"-"+j}
        value={this.props.squares[i][j]}
        onClick={()=>this.props.onClick(i,j)}
        className={className}
      />
    )
  }
```

### mapStateToProps
Reducersが更新したstate（後述）を受け取り、Componentに渡すプロパティ（`props`）の値として割り当てる。**この時`state.`に続くプロパティのキーは、各Reducersの名前（後述）になることに注意。**

```javascript:containers/BoardContainer.js
function mapStateToProps(state) {
    return {
        squares: state.mark.history[state.mark.stepNumber].squares,
        winner: state.mark.winner
    }
}
```

Componentは受け取った**`props`を使ってレンダリングし直すことで、インプットの結果を画面上に反映する。**

```js:components/Board.js
class Board extends React.Component {
……
render() {
    return (
      <div>
        {this.props.squares.map((value,i)=>{
          return(
            <div key={"row-"+i} className="board-row">
              {this.props.squares[i].map((value,j)=>{
                return(
                  this.renderSquare(i,j)
                );
              })
              }
            </div>
           );
        })}
      </div>
    );
  }
}
```

### connect
最後に`mapStateToProps`と`mapDispatchToProps`引数としてReact-Reduxの`connect`を実行し、Containerを生成する。

```javascript:containers/BoardContainer.js
const BoardContainer = connect(
    mapStateToProps,
    mapDispatchToProps
)(Board)
```

このように作成した各ContainerをひとつのReactComponentにまとめる。（直接`Provider`（後述）に渡すこともできる？）

```javascript:components/App.js
class App extends React.Component {
    render() {
        return (
            <div className="game">
                <div className="game-board">
                    <BoardContainer />
                </div>
                <GameContainer />
            </div>
        );
    }
}
```

## ActionとActionCreator
ActionとActionCreatorは**「それがどんなアクションなのか」だけを定義する。**アクションの振る舞い自体はReducersで定義する。

- Action：アクションの種類を定義するプロパティ。
- ActionCreator：Componentから引き渡された要素を元にActionを生成する。

tictactoeでは「マスをクリックしたアクション＝MARK」、「ボタンをクリックしたアクション＝Move」、「reverseリンクをクリックしたアクション＝REVERSE」を定義している。

```javascript:actions/index.js
export const actions = {
  MARK: 'MARK',
  MOVE: 'MOVE',
  REVERSE: 'REVERS'
}
```

例えば前述のComponent`Board`で、`onClick`が実行された場合はマスのインデックスをActionCreator`putMark`に引き渡し、Actionプロパティが生成される。

```javascript:actions/index.js
export const putMark = (i,j) => {
  return {
    type: actions.MARK,
    position: [i,j]
  }
}
```

## Store
StoreはActionCreatorが生成したActionを受け取り（dispatch）、保存されているstateと一緒にReducersに引き渡す。

少なくともサンプルレベルのReact+Reduxでは、Storeを直接いじるようなコーディングはしない。ただしReact+Reduxでは、初期化時に **`createStore`** でStoreを作成し、Reactのルートコンポーネント **`Provider`** にそのStoreを引き渡す必要がある。

```javascript:index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './components/App'
import gameApp from './reducers'

let store = createStore(gameApp)

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)
```
## Reducers
StoreからActionとstateを受け取って、stateを更新することが最大の役割。

Actionに設定された`type`によってstateの処理を振り分けることが基本。そして各Reducerは必ず新しいstateをreturnする必要がある。

```javascript:reducers/index.js
const mark = (state = initialMarkState,action) => {
    switch(action.type) {
        case actions.MARK:
            ……
            if(state.winner || newSquares[action.position[0]][action.position[1]]) {
                return state
            }
            newSquares[action.position[0]][action.position[1]] = state.xIsNext ? 'X' : 'O';
            return (
                Object.assign({},state,{
                    history: history.concat([{
                        squares: newSquares,
                    }]),
                    stepNumber: history.length,
                    xIsNext: !state.xIsNext,
                    winner: calculateWinner(newSquares)
                })
            )
        case actions.MOVE:
            return (
                Object.assign({},state,{
                    stepNumber: action.index,
                    xIsNext: (action.index % 2) === 0,
                    winner:  calculateWinner(state.history[action.index].squares)
                })
            );
        default:
            return state
    }
}
```

定義された各Reducersは **`combineReducers`** に引き渡し、そのオブジェクトを前述の`createStore`に渡すことで、Storeから起動される。

```javascript:reducers/index.js
const gameApp = combineReducers({
    mark,
    reverse
})
```

以上、初心者が四苦八苦で理解できたところまでを、ザックリとまとめてみました。見当ハズレのことがあれば誰か教えてください！