typescriptでreact/reduxアプリを作るときにどこに何をどのように書けばいい感じになるか（の一例）

## 概要

typescriptを勉強しはじめてみて、すごく便利なことはわかったが、react/reduxアプリを書くときにどのようにすればtypescriptの恩恵をより良く受けられるのかがわからなかったので、調べてまとめてみた。この記事で書くのはあくまで個人的に気に入った方法であり、微妙なところや他の方法のほうが良いところがかなりあると思うので、ぜひ教えてください。

## 読んだ資料

typescriptと、typescript/react/reduxの組み合わせ方について勉強した内容をまとめておく。

まず、react云々の前に、typescriptの基礎を理解していなかったので、公式のドキュメントを読んだ。難しそうなところやとりあえず必要なさそうな雰囲気がするところは適当に読み飛ばしつつ通読した（読み飛ばさず全部読むのが良いと思います）。

- [Documentation - typescript](https://www.typescriptlang.org/docs/home.html)

次に、確認の意味もこめて以下のqiita記事を読んだ。内容が大変わかりやすいのと、日本語なので読みやすい。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)

最後に、typescriptとredux+reactの組み合わせ方を勉強した。特にreduxのaction creator周りは普通に書くとtypescriptと組み合わせるのが難しいようで、いろんな工夫の仕方があることがわかった。特に参考になったのは以下。

- [react-redux-typescript-guide](https://github.com/piotrwitek/react-redux-typescript-guide#action-creators)
- [TypeScript + Reduxはもうぼちぼちサードライブラリに頼らなくてもある程度はいい感じに補完してくれる](https://qiita.com/terrierscript/items/b3f9dd95a4c7afe0b102)
- [Writing Better Redux’s Code with TypeScript](https://hackernoon.com/writing-better-reduxs-code-with-typescript-b7a3c1209bca)

いくつかの記事の方法の良さそうなところを取り上げて、自分なりに納得できる方針を考えてみた。

## Todoアプリの例

typescriptでreact/reduxアプリを書く方針を考える/記事にするために、例としてtypescript/react/reduxでTodoアプリを作ってみた。アプリの仕様は [公式ドキュメントのTodoアプリ](https://redux.js.org/basics/example) とだいたい同じです。

githubレポジトリはこちら -> [todo-ts](https://github.com/muiscript/todo-ts)
デモ -> [github pages](https://muiscript.github.io/todo-ts/)

ディレクトリ構成は以下の通り。最近知った[Ducks Pattern](https://github.com/erikras/ducks-modular-redux)というパターンに従ってmoduleに分けている（と言っても今回はmoduleが１つしかないが）。

```shell
src
├── components
│   ├── App.tsx
│   ├── FilterSelect.tsx
│   ├── Form.tsx
│   └── TodoList.tsx
├── index.tsx
└── redux
    ├── modules
    │   └── todos.ts
    ├── store.ts
    └── types.ts
```

#### actionについて

人それぞれでもっともやり方が分かれそうなaction周りの定義。今回は、Union型を使って、moduleごとに全てのactionをまとめた型を作ることにした。javascriptで書くと、タイポを防いだり補完を効かせる目的で`const ADD_TODO = 'ADD_TODO'`みたいな定義をすると思うが、typescriptだと文字列リテラルが型として働くので以下のように書くだけで快適になる。

```typescript
export interface Action<T> {
  type: T;
}
export interface PayloadAction<T, P> {
  type: T;
  payload: P;
}

// moduleごとに全てのactionをまとめた型を作る
export type TodosAction =
  | PayloadAction<'ADD_TODO', { title: string }>
  | PayloadAction<'TOGGLE_TODO', { id: string }>
  | PayloadAction<'CHANGE_FILTER', { filter: Filter }>;

// ↑のおかげでaction creatorを書くときにいい感じに補完が効くようになっている
export const addTodo = (title: string): TodosAction => ({
  type: 'ADD_TODO',
  payload: { title },
});
export const toggleTodo = (id: string): TodosAction => ({
  type: 'TOGGLE_TODO',
  payload: { id },
});
export const changeFilter = (filter: Filter): TodosAction => ({
  type: 'CHANGE_FILTER',
  payload: { filter },
});
```

#### reducerについて

reducerは特に悩むところがなかった気がする。actionで型をつけたおかげで補完が気持ちよく効くことに感謝してたら書き終わった。

```typescript
export const TodosReducer: Reducer<TodosState, TodosAction> = (
  state = todosInitialState, action: TodosAction,
): TodosState => {
  switch (action.type) {
  case 'ADD_TODO':
    return {
      ...state,
      todos: [
        ...state.todos,
        { id: uuid(), title: action.payload.title, completed: false },
      ],
    };
  case 'TOGGLE_TODO':
    return {
      ...state,
      todos: state.todos.map((todo) => (todo.id !== action.payload.id) ? todo : {
        ...todo,
        completed: !todo.completed,
      }),
    };
  case 'CHANGE_FILTER':
    return {
      ...state,
      filter: action.payload.filter,
    };
  default:
    return state;
  }
};
```

#### componentについて

`bindActionCreator`を使うと、`DispatchAction`をうまく書くのが難しい。今回は、`bindActionCreator`を使いつつちゃんと型が働くように、それぞれのcomponentで使うactionをまとめたobjectを作った。

```typescript
// このcomponentで使う全てのactionをまとめたobjectを作る
const actions = { toggleTodo };

interface StateProps {
  visibleTodos: Todo[];
}
// DispatchPropsはactionsを使ってこのように定義しておくと良い
type DispatchProps = typeof actions;

const TodoListPresentation = (props: StateProps & DispatchProps) => (
  <div>
    <ul>
      {props.visibleTodos.map((todo) => (
        <li
          key={todo.id}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onClick={() => props.toggleTodo(todo.id)}
        >
          {todo.title}
        </li>
      ))}
    </ul>
  </div>
);

const mapStateToProps = (state: AppState): StateProps => {
  const { todos, filter } = state.todos;

  return {
    visibleTodos: filterTodos(todos, filter),
  };
};
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
  return {
    ...bindActionCreators(actions, dispatch),
  };
};
```

#### まとめ

typescriptでreact/reduxアプリを書く上で、とりあえず今の自分が納得できる方針を見つけられて良かった。これからtypescriptの理解が進むにつれて、書き方をアップデートしていきたい。より良い方法があると思うので、ぜひ教えてください。