redux-thunk入門、簡単まとめ

# はじめに
（2020.4.23 簡単な例を追加、Tight Coupling関連の記述を修正しました。）

昔ReactとReduxを触ったこと全然ないので、インターンはじめて以来、ただ「こういう風に書くもの」、「このように書くのは正しい」としか認識していない状況でした。そもそもどの部分が `redux-thunk` なのかも全然分かってません。
なので、今回は `redux-thunk` [公式に推奨されてる文章](https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60)を読んで、自分なりに理解した後、記録してみます。
全文を翻訳するわけではないので、詳細はそちらに参照していただければと思います。
ライブラリーは何故作られたのか、どういう問題を解決したのか、それを理解するのはやはり大事なことだと思います。
# Thunkとは
一般的にいうと、`functional programming`のテックニックの一つで、そのまま関数Aを利用するのではなく、まず関数Bに変数を提供して、関数Bはそれを使って関数Aの中身を完成させる。最後は完成した関数Aを返す、必要な時に関数Aを呼び出す感じになってます。
コードだとこういう感じになります。

```js
function yell (text) {
  console.log(text + '!')
}

yell('bonjour') // 'bonjour!'

function thunkedYell (text) {
  return function thunk () {
    console.log(text + '!')
  }
}

const thunk = thunkedYell('bonjour') // まだ実行されてない

thunk() // 'bonjour!' //必要時に呼ぶ

```

で、React / Redux はどういう風にThunkの仕組みを利用しているのかというと、主には`actions`、 `action creators`、 `components`が「直接的に」データに(Asyncなどによる)影響を起こさせないようにしています。
それらの処理は全部Thunkに包んで、そのあとmiddlewareがThunk呼ぶ時に実行されます。
このようの仕組みだと、少なくともMiddlewareレベル以外のところは比較的にピュア（Async関連の処理をしない）になるので、メンテナンス、テスト、読みやすさでは役に立ちます。
# Redux-thunkが作られた理由
## reduxだけ利用すると発生する問題
Reduxのdispatchの引数はaction object。

```js
const LOGIN = 'LOGIN'
store.dispatch({ type: LOGIN, user: {name: 'Lady GaGa'} })
```

で、Asyncのリクエストが組み込まれた場合は：(Axiosを例としている)

```js

const asyncLogin = () =>
  axios.get('/api/auth/me')
  .then(res => res.data)
  .then(user => {
    // 疑問：このuserはどうやって利用する?
    // ここで直接Storeに送るか?
  })

// それに、componentのどこかで:
store.dispatch(asyncLogin()) // こういう風にもできない; `asyncLogin()` は promise、actionではない
```


## 解決法
一見では、async handlerの中で `store.dispatch` 呼ぶことで確かに解決はできますが：

```js
import store from '../store'

const simpleLogin = user => ({ type: LOGIN, user })

const asyncLogin = () =>
  axios.get('/api/auth/me')
  .then(res => res.data)
  .then(user => {
    //直接Storeに送るか！
    store.dispatch(simpleLogin(user))
  })

// で、componentのどこかで:
asyncLogin()
```

さて、これだといくつかの問題があります。

### 問題１：Inconsistent API
このように書くと、`components `の中の処理は二種類に別れることになります。
一つは`store.dispatch(syncActionCreator())`のように、`dispatch`を呼ぶ処理と、
`doSomeAsyncThing()`のような処理。
それでは一致性を失うことになります。

後者の処理ではdispatch関連の部分がどう処理されてるのかのを理解しにくい。
それに、もし処理をsyncからasyncに変更する場合（逆も然り）は、`component`の中の関連記述も修正しなければならないので、メンテナンス上では非常に手間がかかります。

### 問題２：Impurity
前述した`asyncLogin`は明らかにピュアな関数ではありません。それ自体はまあ仕方がないが、このように`component`が直接にデータに影響を起こすと、一見では悟りにくいので、メンテナンス上でも、`unit testing`上(例えばaxiosをmockする)でもよしとされません。

### 問題３：Tight Coupling
`asyncLogin`(action creator)の利用は一つ特定の`Redux store`に限定されています。
（たとえばテストする時にMockしたい）

## Thunkでの解決法を試みる
Thunkを使って、network関連の処理は「直ちに」実行させない、
その代わりにThunkの中に包んで、Thunkを返す。

```js
import store from '../store'   // 今はバインドしているのでTight Coupling

const simpleLogin = user => ({ type: LOGIN, user })

const thunkedLogin = () =>    
  () =>                        
    axios.get('/api/auth/me')
    .then(res => res.data)
    .then(user => {
      store.dispatch(simpleLogin(user))
    })

// componentのどこかで:
store.dispatch(thunkedLogin()) // thunk自体をStoreに送…れるのか？

```

これでは、問題１の`doSomeAsyncThing()`は`component`の中に存在しなくなります、`thunkedLogin`も比較的にピュアになります（実行される時にThunkを返すだけなので）。

でも待って、`action creator`が返すのは`action object`ではないので、`redux`は処理できないはずでは？

そう、本来の`redux`だけを使うとできない。
`redux`が`thunk`を処理できるようにするのが`redux-thunk`なのです。

# Redux-Thunk Middleware
redux-thunkがインストールされると、Dispatchはこのようのものに変えられます：

```js
actionOrThunk =>
  typeof actionOrThunk === 'function'
    ? actionOrThunk(dispatch, getState)
    : passAlong(actionOrThunk);
```
ようするに：
- Dispatchされたのは一般の`action object`だと、そのまま通す。
- Dispatchされたのは関数(すなわちthunk)になると、その関数に`store`の`dispatch`と`getState`を渡して実行させる。


あとは、Tight Couplingの問題が残っていますが、それも`redux-thunk`がDIを通じて解決しました。
普通のthunkは引数を取らないが、`redux-thunk`はその型を破って、接続した`store`の`dispatch`と`getState`をthunkに渡している。
すなわち、`action creators`は`store`を指定してない、`dispatch`だけもらってそのままを使う。
ここでもし`redux-thunk`が接続したのはmockした`store`であれば、生成された`actions`はmockした`store`に`dispatch`されます。
これで`store`を簡単にに変えられますね。

# Redux-Thunkを使った例
actions.ts

```typescript
import axios from 'axios'
import * as actionTypes from './actionTypes'

export function fetchAll(): (dispatch: any) => any {
  return dispatch => {
    dispatch({ type: actionTypes.FETCH_USERS})
    axios.get('/api/users').then(response => {
      dispatch({
        type: actionTypes.FETCH_USERS_SUCCESS,
        payload: response.data,
      })
    })
  }
}
```

TestPage.tsx

```tsx

import { fetchAll } from 'actions'
import { useDispatch } from 'react-redux'

export const TestPage: React.FC = () => {
  const dispatch = useDispatch()
  return (<button onClick={() => dispatch(fetchAll())}>Fetch</button>)
}
```

# 終わりに
何回も読みながら情報整理した後、ようやく`redux-thunk`の基礎を理解した…気がしなくてもない。
本来の文章では他の内容ものってるので、よければそちらも参照してみてはいかがでしょうか。
何か間違ってる部分や不自然な日本語がありましたら、コメントを頂けると嬉しいです。