Reactでredux-sagaで非同期通信 〜redux-thunkとの比較も〜

# Reactでの非同期通信処理の方法
Reduxのミドルウェアを使って実装します

※ Reactのコンポーネント内で処理する方法もありますが、コンポーネントと通信処理が密結合になってコンポーネントのソースが複雑になり、可読性が下がるなどの理由で採用されるケースはあまりないためこの方法はとりません


# redux-sagaとredux-thunkの比較
非同期通信処理の代表である redux-sagaとredux-thunkについて比較してみます

## redux-thunk
![redux-thunk_arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274080/df7f31f7-81ec-83f0-64a8-f0f49e9da73e.png)

上図のように、redux-thunkを導入すると、action creatorの中に非同期通信処理を書いたり、action creatorからさらに別のaction creatorをcallすることができるようになります。

そのため、本来の **reduxのアーキテクチャの形から逸脱することになり、記述の自由度が上がり開発者各々が書くことでソースコードの可読性が下が**ってしまいます。

## redux-saga
![redux-saga_arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274080/8387c220-876b-1592-cf80-653d39562c1e.png)

・Actionが発行→ DispatcherはStoreとSagaのスレッドにActionを渡す
・非同期処理をTaskとして登録しておく。このTaskはアプリ起動時からスレッドのように実行待ちになる
・Actionが渡されるとWatch TaskはそれをキャッチしTaskを実行。実行結果をAction Creatorに渡す

ポイントは **Reduxとは独立して動く**

また、テストも書きやすくなっています。

![スクリーンショット 2019-07-28 19.34.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274080/5766a77e-7e7e-245b-9229-0d731891d3cf.png)
【↑ Redux-Sagaのロゴ。Reduxのロゴと独立していることを示している】


|  | redux-saga | redux-thunk |
|:-----------|------------:|:------------:|
| サイズ       | 14KB        | 352B         |
| Githubスター数     | 18,450      | 12,778       |
| 記述量       | 多い        | 少ない         |
| Action Creator         |   入れ子にならない        | 入れ子になるためコールバックが連なり可読性が下がる           |


# redux-sagaの実装例
reduxの公式サンプルの counterを改造してボタンが押されてから1秒後にカウントを追加する非同期処理を実装していきます

参考
・[redux-saga official beginner tutorial](https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html)
・[ソース](https://github.com/redux-saga/redux-saga-beginner-tutorial/tree/sagas)

##
sagaを実行するために以下を実装します

1. MiddleWareを作成
2. 作成したMiddlewareを ReduxのStoreとconnectする

例)main.js

```
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import Counter from './Counter'
import reducer from './reducers'
import { helloSaga } from './sagas'

// sagaMiddlewareを作成
const sagaMiddleware = createSagaMiddleware()
// SagaMiddlewareをStoreとconnect
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
// SagaMiddlewareを実行
sagaMiddleware.run(helloSaga)

const action = type => store.dispatch({type})
・・・
```

こちらが実行するSaga(helloSaga)。 SagaMiddleware実行時に引数に渡しています
saga.js

```
export function* helloSaga() {
  console.log('Hello Sagas!')
}
```

## 非同期通信処理
クリックしてから１秒後に1カウントする処理を実行するボタンを追加
Counter.js

```
const Counter = ({ value, onIncrement, onDecrement, onIncrementAsync }) =>
  <div>
    <button onClick={onIncrementAsync}>
      Increment after 1 second
    </button>
    {' '}
   ...
  </div>
```

`onIncrementAsync ` をStoreのactionにをconnectします
main.js

```
function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => action('INCREMENT')}
      onDecrement={() => action('DECREMENT')}
      onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
    document.getElementById('root')
  )
}
```

非同期通信をするために、 Taskと TaskWatcherを定義します

```
import { put, takeEvery } from 'redux-saga/effects'

const delay = (ms) => new Promise(res => setTimeout(res, ms))

// ...

// worker Saga: 非同期のincrement taskを実行します
export function* incrementAsync() {
  yield delay(1000) // Generatorをとめる働きをします。
  yield put({ type: 'INCREMENT' })
}

// TaskWatcher: INCREMENT_ASYNCのたびにincrementAsync taskを新たに作成します
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
```

actionのtypeが `INCREMENT_ASYNC` の場合に `incrementAsync()`を実行します


#### Effect API

put: Action Creatorを実行してActionをdispatchします
他にも、select, join, callなどがあります
参考：https://redux-saga.js.org/docs/api/


## 複数のSagaを同時に実行

sagas.js

```
export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
}
```

上で定義した rootSagaをmiddleware実行時に引数に渡すことで
それぞれのsagaが平行に実行されます

main.js

```
// ...
import rootSaga from './sagas'

const sagaMiddleware = createSagaMiddleware()
const store = ...
sagaMiddleware.run(rootSaga)

// ...
```

# まとめ
reduxのアーキテクチャーをそのままに独立したものとして非同期処理を実装でき、redux-thunkと違い書き方が統一されるため、redux-sagaで非同期処理を実装するのがいいと思います。

## 参考

[redux-saga officai doc](https://redux-saga.js.org/)
[redux-saga official github](https://github.com/redux-saga/redux-saga/)
[redux-saga README_ja](https://github.com/redux-saga/redux-saga/blob/master/README_ja.md)

[redux-thunk github](https://github.com/reduxjs/redux-thunk)
[りあクト！](https://booth.pm/ja/items/1312652)