ReduxをTypeScriptで入門する on June, 2019

[こちら](https://qiita.com/awa2/items/d83a5a614bf99994bbd8)の続き

# Redux
Reactの状態管理は、**Redux**がベストプラクティスとのこと。
状態管理をReduxでやる方法を入門します。

## なんやのRedux

 * Reactの状態であるStateを管理するもの
    * Storeというもので状態を一元管理する

たしかに、React書きはじめると、stateをどんどん上に持たせたくなるので、その延長と捉えると良さそうです。

## How to use with TypeScript?
`typescript-fsa`というパッケージを使えば、Actionの静的型付けをよしなにしてくれるとのこと。

## 環境づくり
```bash
$npm install -S redux react-redux typescript-fsa typescript-fsa-reducers
```


# Action、 Action Creator

 * 何らかの変更を通知するのが**Action**
 * Actionを作ってくれる関数が**Action Creator**

```JavaScript:Action
{
  type: 'ADD_TODO', // ここがType
  text: 'Build my first Redux app' // ここがPayload
}
```
上記のようなObject=Actionをやり取りするわけですね。
これを関数でラップしたようなのがActionCreator。

```JavaScript:ActionCreator
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
```

`typescript-fsa`では、これをひとまとめにきれいにしてくれて、

```TypeScript:typescript-fsaを利用した場合
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

export const todoActions = {
  addTodo: actionCreator<string>('ACTIONS_ADD_TODO')
};
```
と書けるようです。
ここの`actionCreator<T>` にPayloadの型を指定しておくと、ジェネリクスにより型情報が伝わる仕組みです。

# Reducer
 * Actionを受け取ったら、加工して、新しい状態のStateを返すものが**Reducer**
    * 状態用のロジック
    * これによってStateを更新する

今回は`typescript-fsa-reducers`を使います。

## ActionCreator & Reducerを一緒に

```TypeScript:src/module/Todo.tsx
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory();

export const addTodo = actionCreator<string>('todo/ADD_TODO');
export const toggleTodo = actionCreator<number>('todo/TOGGLE_TODO');
export const ActionCreator = {
  addTodo,
  toggleTodo,
};

export interface State {
  id: number,
  text: string,
  completed: boolean,
}

const initialTodoState: State[] = [];

export const Reducer = reducerWithInitialState(initialTodoState)
  .case(addTodo, (state, payload) => {
    return [
      ...state,
      {
        id: state.length,
        text: payload,
        completed: false
      }
    ]
  })
  .case(toggleTodo, (state, payload) => {
    return state.map(todo => {
      return todo.id === payload ? { ...todo, completed: !todo.completed } : todo;
    })
  })
  .default((state) => {
    return state;
  });
```

ActionCreatorとReducerのファイルを分けておくほうが良いようですが、今回は１つにまとめています。

# Store

 * データの保存場所が**Store**
    * Storeの生成は`createStore()`でReducerから生成する

いったんRootState, rootReducerとして、StateやReducerをまとめておきます。
Reducerをまとめるときは`combineReducers()`を使います。

```TypeScript:src/module/index.tsx
import { combineReducers } from 'redux';
import * as Todo from './Todo';
import * as VisibilityFilter from './VisibilityFilter';

export type RootState = {
  todos: Todo.State[],
  visibilityFilter: VisibilityFilter.State
}

export const rootReducer = combineReducers({
  todos: Todo.Reducer,
  visibilityFilter: VisibilityFilter.Reducer
})

export const store = createStore(rootReducer);

export const actionCreator = {
  todos: Todo.ActionCreator,
  visibilityFilter: VisibilityFilter.ActionCreator
};
```

# Presentational Components

 * Reduxに依存しないコンポーネント
 * Propsを受け取ってレンダリングされるView

```TypeScript:src/components/TodoList.tsx
import * as React from 'react';
import { State } from '../module/Todo';
import Todo from './Todo'


type OwnProps = {
  toggleTodo: (id: number) => void;
}

export type Props = { Todos: State[] } & OwnProps;

const TodoList: React.FunctionComponent<Props> = (props) => (
  <ul>
    {props.Todos.map(todo => (
      <Todo
        key={todo.id}
        id={todo.id}
        text={todo.text}
        completed={todo.completed}
        onClick={() => {
          props.toggleTodo(todo.id)
        }} />
    ))}
  </ul>
);

export default TodoList;
```

Stateは持っていないので、Propsを受け取ってViewを作るだけの処理にするようです。
Propsの型は自前で定義しますが、すでにReducerやActionCreatorで定義されているものがあれば、読み込んでおけば楽になりそう。

# Container

 * 一番上のレイヤーのコンポーネント
 * ReduxとつながっているView
 * 最小のアプリケーション単位としてContainerが１つあるイメージっぽい

```TypeScript
import { Dispatch } from 'redux';
import { connect } from 'react-redux'

import { RootState } from '../module/index';
import { ActionCreator } from '../module/Todo';
import TodoList from '../components/TodoList';

const mapStateToProps = (state: RootState) => {
    const filter = () => {
        switch (state.visibilityFilter.VisibilityFilter) {
            case 'SHOW_ALL':
                return state.todos;
            case 'SHOW_COMPLETED':
                return state.todos.filter(e => e.completed);
            case 'SHOW_ACTIVE':
                return state.todos.filter(e => !e.completed);
            default:
                throw new Error('Unknown filter.');
        }
    };
    return {
        Todos: filter()
    }
}

const mapDispatchToProps = (dispatch: Dispatch) => {
    return {
        toggleTodo: (id: number) => {
            dispatch(ActionCreator.toggleTodo(id));
        }
    }
}

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

## mapStateToProps
Storeから必要なものを抽出して、コンポーネントにわたす処理を行う。

```TypeScript
const mapStateToProps = (store: Store) => ({ user: store.user });
connect(mapStateToProps)(Component);
```

 * これでStoreから取り出された`{ user: store.user }`が`Component`に渡る
 * Component側ではPropsに渡っていく

## mapDispatchToProps
ActionをComponentに紐付ける処理を行う。

```TypeScript
const mapDispatchToProps = (dispatch: Dispatch) => {
  return {
    updateName: (name: string) => dispatch(ActionCreator.updateName(name))
  }
}
connect(null, mapDispatchToProps)(Component);
```

 * 事前に定義していたActionCreatorをdispatchする
 * Component側ではPropsに関数として渡っていく

## mergeProps

 * `mapStateToProps`, `mapDispatchToProps`の上位概念
 * 渡す`Props`を`Object.assign`で作る
    * マニュアルで生成するようなもの

# 参考文献
 * [たぶんこれが一番分かりやすいと思います React + Redux のフロー図解](https://qiita.com/mpyw/items/a816c6380219b1d5a3bf)
 * [Redux ExampleのTodo Listをはじめからていねいに(1)](https://qiita.com/xkumiyu/items/9dfe51d2bcb3bdb06da3)
 * [reduxをtypescriptで使うならこれを使うしかない。(typescript-fsaがすごい)](https://qiita.com/m0a/items/703d64c74e43cb392a64)
 * [React + Redux + TypeScriptのシンプルなサンプル](https://qiita.com/naito-y/items/df49b3756a3896a5b5ac)
 * [関東最速でReact+Redux+TypeScriptなアプリを作る](https://qiita.com/IzumiSy/items/b7d8a96eacd2cd8ad510)
 * [react-reduxのmapStateToPropsとmapDispatchToPropsは必要なのか](https://anect.hatenablog.com/entry/2017/06/09/151000)
 * [大事なのはmergeProps、そして退かぬ心じゃ](https://anect.hatenablog.com/entry/2018/04/18/190841)
 * [Reduxのreselectとは](https://qiita.com/zaki-yama/items/5258e6f1ae37f63034b9)