Flow と TypeScript のTodoチュートリアルコード集

## 概要
FlowとTypeScriptの実際の違いについて、Todoアプリで試してみることにしました。
Todoアプリのソース自体は、Reduxの公式サイトから[Example: Todo List](https://redux.js.org/basics/example)をそのまま使用しています。

### 参考になった記事

TypeScriptのサンプルコードはこちらが見やすいです。

* [ReactをTypeScriptで書ける環境で、ReduxのTutorialをしてみる](https://qiita.com/IgnorantCoder/items/88f13569cbf0a1c5eaa1)
* [TodoList簡易版@Typescript+React+Redux](https://qiita.com/knknkn1162/items/94543bf8c0701f25ac77)
* [Redux Example の TODO List を TypeScript で作成](https://qiita.com/NariseT/items/7b5d0e1bc7ba1d21c35b)
* [react-redux-typescript-guide](https://github.com/piotrwitek/react-redux-typescript-guide/blob/master/README.md)


Flowのサンプルコードはこちらが見やすいです。

* [Redux Todos Example](https://github.com/reduxjs/redux/tree/master/examples/todos-flow)


## Flow vs TypeScript
ここから実際の記述です。（🔰初心者です。予めご了承ください🙇‍♂）

### 型定義ファイル
#### types/index.js → types/index.ts
<img width="921" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/c66eaed6-4f8d-968a-d619-5208faba5a7b.png">

##### [Flow] types/index.js
```javascript
// @flow
import type { Store as ReduxStore, Dispatch as ReduxDispatch } from 'redux'
import type { TodosState, TodosAction } from './todos'
import type {
  VisibilityFilterState,
  VisibilityFilterAction,
} from './visibilityFilter'

type ReduxInitAction = { type: '@@redux/INIT' }

export type State = TodosState & VisibilityFilterState

export type Action = ReduxInitAction | TodosAction | VisibilityFilterAction

export type Store = ReduxStore<State, Action>

export type Dispatch = ReduxDispatch<Action>
```

##### [TS] types/index.ts
```javascript
import { TodosState, TodosAction } from './todos'
import {
  VisibilityFilterState,
  VisibilityFilterAction,
} from './visibilityFilter'

export type State = TodosState & VisibilityFilterState

export type Action = TodosAction | VisibilityFilterAction
```


#### types/todos.js → types/todos.ts
<img width="920" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/7627eba7-7896-245c-f287-b09e1e7cab7c.png">

##### [Flow] types/todos.js
```javascript
// @flow

export type Id = number

export type Text = string

export type Todo = {
  +id: Id,
  +text: Text,
  +completed: boolean,
}

export type Todos = Array<Todo>

export type TodosState = {
  +todos: Todos,
}

export type AddTodoAction = {
  type: 'ADD_TODO',
  id: Id,
  text: Text,
}

export type ToggleTodoAction = {
  type: 'TOGGLE_TODO',
  id: Id,
}

export type TodosAction = AddTodoAction | ToggleTodoAction
```

##### [TS] types/todos.ts
```javascript
import { Action } from 'redux'

export type Id = number

export type Text = string

export type Todo = {
  id: Id
  text: Text
  completed: boolean
}

export type Todos = Todo[]

export type TodosState = {
  todos: Todos
}

export interface AddTodoAction extends Action {
  type: 'ADD_TODO'
  id: Id
  text: Text
}

export interface ToggleTodoAction extends Action {
  type: 'TOGGLE_TODO'
  id: Id
}

export type TodosAction = AddTodoAction | ToggleTodoAction
```

#### types/visibilityFilter.js → types/visibilityFilter.ts
こちらのファイルはFlowとTypeScriptであまり違いはありません。

```javascript
// @flow 👈 これがなくなるだけ

export type VisibilityFilter = 'SHOW_ALL' | 'SHOW_ACTIVE' | 'SHOW_COMPLETED'

export type VisibilityFilterState = {
  visibilityFilter: VisibilityFilter
}

export type VisibilityFilterAction = {
  type: 'SET_VISIBILITY_FILTER'
  filter: VisibilityFilter
}
```

### Actions
#### actions/index.js → actions/index.ts
こちらのファイルはFlowとTypeScriptであまり違いはありません。

```javascript
// @flow 👈 これがなくなるだけ

import { Id, Text, TodosAction } from '../types/todos'
import {
  VisibilityFilter,
  VisibilityFilterAction,
} from '../types/visibilityFilter'

let nextTodoId = 0

export const addTodo = (text: Text): TodosAction => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text,
})

export const toggleTodo = (id: Id): TodosAction => ({
  type: 'TOGGLE_TODO',
  id,
})

export const setVisibilityFilter = (
  filter: VisibilityFilter
): VisibilityFilterAction => ({
  type: 'SET_VISIBILITY_FILTER',
  filter,
})
```


### Reducers
#### reducers/index.js → reducers/index.ts
`combineReducers`のところで書き方が少し違います。

<img width="904" alt="3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/0fd57ac4-1640-d3e3-4571-f9f69bf22b42.png">


##### [Flow] reducers/index.js
```javascript
// @flow

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
import type { Action } from '../types'

export default combineReducers<Object, Action>({
  todos,
  visibilityFilter,
})
```

##### [TS] reducers/index.ts
```javascript
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter,
})
```

#### reducers/todos.js → reducers/todos.ts
こちらのファイルはFlowとTypeScriptであまり違いはありません。

<img width="931" alt="4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/1b9e8f0a-4647-8a92-7673-2ca59ca89b2f.png">

##### [TS] reducers/todos.ts
```javascript
import { Todo, Todos } from '../types/todos'
import { Action } from '../types'

const todos = (state: Todos = [], action: Action): Todos => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false,
        },
      ]
    case 'TOGGLE_TODO':
      return state.map(
        (todo: Todo): Todo =>
          todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    default:
      return state
  }
}

export default todos
```

#### reducers/visibilityFilter.js → reducers/visibilityFilter.ts
こちらのファイルはFlowとTypeScriptであまり違いはありません。

<img width="895" alt="5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/21529cec-642f-61e8-83da-11b180ffe90e.png">

##### [TS] reducers/visibilityFilter.ts
```javascript
import { VisibilityFilter } from '../types/visibilityFilter'
import { Action } from '../types'

const visibilityFilter = (
  state: VisibilityFilter = 'SHOW_ALL',
  action: Action
) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default visibilityFilter
```

### containers
Reduxの`connect`や`mapDispatchToProps`あたりの実装です。

#### containers/AddTodo.js → containers/AddTodo.ts
##### [Flow] containers/AddTodo.js
```javascript
// @flow

import { connect } from 'react-redux'
import { addTodo } from '../actions'
import TodoForm from '../components/TodoForm'

import type { Dispatch } from '../types'

export type Props = {
  dispatch: Dispatch,
}

const mapDispatchToProps = (dispatch: Dispatch) => ({
  onSubmit: (text) => dispatch(addTodo(text)),
})

export default connect(
  null,
  mapDispatchToProps
)(TodoForm)
```

##### [TS] containers/AddTodo.ts
```javascript
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
import TodoForm from '../components/TodoForm'
import { Action } from '../types'
import { Text } from '../types/todos'

export type Props = {
  dispatch: Dispatch
}

const mapDispatchToProps = (dispatch: Dispatch<Action>) => ({
  onSubmit: (text: Text) => dispatch(addTodo(text)),
})

export default connect(
  null,
  mapDispatchToProps
)(TodoForm)
```


#### containers/FilterLink.js → containers/FilterLink.ts
`Dispatch`の参照先が変わるだけです。

<img width="913" alt="6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/5365a943-9b5c-1a26-7220-c36f90c74fa5.png">

##### [TS] containers/FilterLink.ts
```javascript
import { Dispatch } from 'redux' // 👈 TS
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
import { State, Action } from '../types'
// 👆 Flowの時
// import type { State, Dispatch } from '../types'
import { VisibilityFilter } from '../types/visibilityFilter'

type OwnProps = {
  filter: VisibilityFilter
}

const mapStateToProps = (state: State, ownProps: OwnProps) => ({
  active: ownProps.filter === state.visibilityFilter,
})

const mapDispatchToProps = (
  dispatch: Dispatch<Action>,
  ownProps: OwnProps
) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
})

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

#### containers/VisibleTodoList.js → containers/VisibleTodoList.ts
こちらも`Dispatch`の参照先が変わるだけです。
<img width="916" alt="7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/d1097e3a-ee75-af91-c933-f461879dc3a7.png">

##### [TS] containers/VisibleTodoList.ts
```javascript
import { connect } from 'react-redux'
import { Dispatch } from 'redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { State, Action } from '../types'
import { Todos } from '../types/todos'
import { VisibilityFilter } from '../types/visibilityFilter'

const getVisibleTodos = (todos: Todos, filter: VisibilityFilter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter((t) => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter((t) => !t.completed)
    default:
      throw new Error('Unknown filter: ' + filter)
  }
}

const mapStateToProps = (state: State) => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter),
})

const mapDispatchToProps = (dispatch: Dispatch<Action>) => ({
  toggleTodo: (id: number) => dispatch(toggleTodo(id)),
})

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

### components
App.jsx、Footer.jsxは型定義が必要ないため、それ以外の
Link.jsx, Todo.jsx、TodoForm.jsx、TodoList.jsxに型定義していくことにします。

#### components/Link.jsx → components/Link.tsx
<img width="917" alt="8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/0b424f5f-68ac-d43d-06e0-b678ff0c7e9c.png">

##### [Flow] components/Link.jsx

```javascript
// @flow

import React from 'react'
import type { Node } from 'react'

export type Props = {
  active: boolean,
  children?: Node,
  onClick: () => void,
}

const Link = ({ active, children, onClick }: Props) => (
  <button
    onClick={onClick}
    disabled={active}
    style={{
      marginLeft: '4px',
    }}
  >
    {children}
  </button>
)

export default Link
```

##### [TS] components/Link.tsx

```javascript
import * as React from 'react'

export type Props = {
  active: boolean
  children?: React.ReactNode
  onClick: () => void
}

const Link: React.SFC<Props> = ({ active, children, onClick }: Props) => (
  <button
    onClick={onClick}
    disabled={active}
    style={{
      marginLeft: '4px',
    }}
  >
    {children}
  </button>
)

export default Link
```

#### components/Todo.jsx → components/Todo.tsx
こちらのファイルはFlowとTypeScriptであまり違いはありません。

<img width="931" alt="9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/a54b377f-847c-bbef-e572-4296770f359d.png">

##### [TS] components/Todo.tsx
```javascript
import * as React from 'react'

export type Props = {
  onClick: () => void
  completed: boolean
  text: string
}

const Todo: React.SFC<Props> = ({ onClick, completed, text }: Props) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none',
    }}
  >
    {text}
  </li>
)

export default Todo
```

#### components/TodoForm.jsx → components/TodoForm.tsx
イベントハンドラ系がFlowとTypeScriptで違いがあります。
<img width="1127" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/d95edbf3-dfdc-de8c-97eb-d3448d5beeb5.png">

##### [Flow] components/TodoForm.jsx

```javascript
// @flow

import React from 'react'
import type { Text } from '../types/todos'

export type Props = {
  onSubmit: (text: Text) => void,
}

const TodoForm = ({ onSubmit }: Props) => {
  const [todoName, setTodoName] = React.useState<Text>('')

  const handleChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
    setTodoName(event.target.value)
  }
  const handleSubmit = (event: SyntheticEvent<HTMLButtonElement>) => {
    event.preventDefault()
    onSubmit(todoName)
    setTodoName('')
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" value={todoName} onChange={handleChange} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

export default TodoForm
```

##### [TS] components/TodoForm.tsx

```javascript
import React from 'react'
import { Text } from '../types/todos'

export type Props = {
  onSubmit: (text: Text) => void
}

const TodoForm: React.SFC<Props> = ({ onSubmit }: Props) => {
  const [todoName, setTodoName] = React.useState<Text>('')

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setTodoName(event.target.value)
  }
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    onSubmit(todoName)
    setTodoName('')
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" value={todoName} onChange={handleChange} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

export default TodoForm
```

#### components/TodoList.jsx → components/TodoList.tsx
こちらのファイルもFlowとTypeScriptで違いはさほどありません。
<img width="1126" alt="11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/e9652d83-8c30-fdcc-18e8-882765564859.png">

##### [Flow] components/TodoList.jsx

```javascript
import React from 'react'
import Todo from './Todo'
import { Todos, Id } from '../types/todos'

export type Props = {
  todos: Todos
  toggleTodo: (id: Id) => void
}

const TodoList: React.SFC<Props> = ({ todos, toggleTodo }: Props) => (
  <ul>
    {todos.map((todo) => (
      <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
    ))}
  </ul>
)

export default TodoList
```

## 終わりに
Todoアプリは非同期処理がないので、アプローチとともに、Redux-thunk × TypeScript版もQiitaの記事を書こうと思います！