Reduxについて調べて、実際に触ってみた

##はじめに
以下の2つの記事をめっちゃ参考にしました。

- [React+Redux で Todoアプリを作ってみる │ Web備忘録](https://webbibouroku.com/Blog/Article/react-redux-todo#outline__9)
- [Redux入門【ダイジェスト版】10分で理解するReduxの基礎- Qiita](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)

##何ができる?
Reactのstateを一元管理することができる

##登場人物
* ActionCreator
    * Actionを作成するメソッド
* Action
    * 実行する処理
* Store
    * State置き場
* State
    * データ
* Reducer
    * stateを更新するメソッド

##処理の流れ
- ユーザーが送信ボタン等をクリックする
- ActionCreatorがActionを作成する
- ActionをStoreに送る(dispatchする)
- 受け取ったActionと現在のStateをReducerに渡す
- 新しいStateを作成する
    - 受け取ったActionによってStoreの扱い方を判断する
    - 元のStateを改変するのではなく、新しいStateを作る
- 古いStateを破棄して、新たに作成されたStateを保存する

![スクリーンショット 2015-11-30 1.13.59.png](https://qiita-image-store.s3.amazonaws.com/0/36813/7402c7f4-a7b2-d4a2-e30d-d4ab5816487c.png "スクリーンショット 2015-11-30 1.13.59.png")

以下の記事から拝借しました。
[Redux入門【ダイジェスト版】10分で理解するReduxの基礎](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)

##3原則
- Single source of truth
    - アプリケーション内でStoreは1つだけ。Stateは単独のオブジェクトとしてStoreに保持される。
- State is read-only
    - Stateを直接変更することはできず、actionをStoreへdispatchすることでしかStateは変更できない。
- Mutations are written as pure functions
    - Stateを変更する関数(Reducer)はpureな関数にする。

## Reduxを使ったTODOアプリを作ってみる
###セットアップ
```zsh
$ create-react-app react-redux-todo
$ cd react-redux-todo
$ npm i redux react-redux redux-logger
```
```
$ rm -rf src
$ mkdir src
$ cd src
$ mkdir actions components containers reducers
$ touch actions/Todo.js components/Todo.js containers/Todo.js reducers/Todo.js createStore.js App.js index.js
$ cd ..
```

###Storeを作成
```src/createStore.js
import { createStore as reduxCreateStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import { todoReducer } from "./reducers/Todo";

export default function createStore() {
  const store = reduxCreateStore(
    //複数のReducerを扱うために、combineReducerを利用
    combineReducers({
      todo: todoReducer,
    }),
    applyMiddleware(
      logger,
    )
  );

  return store;
}
```

###Reducer(仮)を作成
```src/reducers/Todo.js
export const todoReducer = (state = {}) => state;
```

###ReactにReduxを組み込む
```jsx:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { Provider } from 'react-redux';
import createStore from './createStore';

const store = createStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>, 
  document.getElementById('root')
);
```
- `createStore`と`react-redux`をimport
- createStoreで`store`を生成
- storeを`Provider`に渡し、Appコンポーネントをラップ

次にApp.jsのひな形を作成。

```jsx:src/App.js
import React from 'react';

function App() {
  return (
    <div>
      Hello World      
    </div>
  );
}

export default App;
```
```zsh
$ npm start
```
とりあえずここまででエラーが発生していないことを確認

###TODOコンポーネントを作成
ひとまず入力エリア、ボタン、リストを作っておきます。

```jsx:src/components/Todo.js
import React from 'react';

export default class Todo extends React.Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>追加</button>
        <ul>
          <li>TODO1</li>
          <li>TODO2</li>
        </ul>
      </div>
    );
  }
}
```

そして`App.js`で読み込みます。

```jsx:src/App.js
import React from 'react';
import Todo from './components/Todo';

function App() {
  return (
    <div>
      <Todo />   
    </div>
  );
}

export default App;
```

###Actionを定義
```src/actions/Todo.js
export const addTodo = (todo) => {
  return { 
    type: 'ADD_TODO',
    payload: { todo: todo }
  };
}
```
- `type`が処理のキーで、全てのactionに必ず1つ設定されています
    - どの`reducer`を使うかの判断材料になります
- `payload`は処理に使うパラメーターです

###Reducerを修正
```src/reducers/Todo.js
const initialState = {
  todoList: []
}

export const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      const todo = action.payload.todo;
      const newState = Object.assign({}, state);
      newState.todoList.push(todo);
      return newState;
    default:
      return state;
  }
};
```
- 現在のstateを定義
- 引数に現在のstateと、受け取ったactionをとったメソッドを作成
- `action.type`に応じて処理を切り分ける
- `Object.assign`メソッドで現在のstateを複製
- 複製したstateに値を追加して返す
    - 元のstateの値を直接編集することはできない点に注意

### Containerを定義
コンテナを定義して、reduxとコンポーネントを結びつけます

```src/containers/Todo.js
import { connect } from 'react-redux';
import * as actions from '../actions/Todo';
import Todo from '../components/Todo';

const mapStateToProps = state => {
  return {
    todo: state.todo,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addTodo: (todo) => dispatch(actions.addTodo(todo)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Todo)
```

- `mapStateToProps`メソッドで、必要なStateをコンポーネントの`props`から参照できるようにします
- `mapDispatchToProps`メソッドで、`actions/Todo.js`で定義したアクションをコンポーネントの`props`から参照できるようにします
- connect関数で`Todo`コンポーネントとの紐付けを行います

最後に、`App.js`でコンポーネントをimportしていた部分を、コンテナをimportするように書き換えます。

```diff:src/App.js
- import Todo from './components/Todo';
+ import Todo from './containers/Todo';
```

### コンポーネントからStateを参照
コンテナからマッピングされて使えるようになった`state`、`dispatch`用のpropsを使うように修正します。

```jsx:src/components/Todo.js
import React from 'react';

export default class Todo extends React.Component {
  state = {
    todo: ''
  }

  render() {
    //console.log(this.props);
    const list = this.props.todo.todoList.map((todo, index) => <li key={index}>{todo}</li>)

    return (
      <div>
        <input type="text" onChange={elm => this.setState({ todo: elm.target.value })} />
        <button onClick={() => this.props.addTodo(this.state.todo)}>追加</button>
        <ul>
          {list}
        </ul>
      </div>
    );
  }
}
```
- `store`の`todoList`から現在のリストの内容を受け取ってリストを作成→表示
- `onChange={elm => this.setState({ todo: elm.target.value })}`でテキスト変更時に入力内容をローカルの`state`に反映
- 「追加」ボタンをクリックした時に、`actions`で定義した`addTodo`メソッドをdispatch
    - 引数にローカルの`state`を渡す
- `createStore.js`で`redux-logger`というミドルウェアをインポートしているので、`store`の`state`が変更されるたびコンソールにログが出力される

### 思ったことなど
- `ActionCreator`はこの仕組みでいうどこの部分?
    - `src/actions/Todo.js`内で定義している関数をActionCreatorと呼ぶらしい
-  `reducer`中、`const initialState = {todoList: []}`でからの配列を定義しているけれど、本来はここにDBから取ってきた値をセットするはず
-  同じく`reducer`内、`action`を受け取った後に発火する関数の中で、サーバーにデータを送信してDBの更新を試みるはず
-  あくまで他のコンポーネントと値をやりとりする時にstoreを経由しないといけないって話なので、自分のコンポーネント内でstateを変更することはできる