【Redux】redux-sagaにおけるtakeEveryとtakeLatestの挙動確認

#はじめに
新しい現場で状態管理にReduxを使っているのですが、ポートフォリオ作成以来触れていなかったので復習することにしました。
今回はredux-sagaのtakeEvery, takeLatestの挙動を確かめました。

#redux-sagaとは
redux-sagaは、React/Reduxアプリケーションにおけるデータ通信などの非同期処理や、ブラウザキャッシュへのアクセス処理を簡単に実装するためのライブラリです。

同じReduxミドルウェアのredux-thunkでデータ通信処理を実装しようとすると、Action Creatorの中で別のAction Creatorを呼び出すなどのいわゆるコールバック地獄に陥る可能性があり、ピュアな(同じ引数を与えたときに同じ結果を返す)関数ではなくなってしまいます。

一方、redux-sagaではアプリケーションの中で副作用を個別に実行する独立したスレッド(Saga)をつくります。
そのため、Reduxとは独立して非同期処理を実行することになります。
redux-thunkのときのようにAction Creatorを汚さずに済み、テストも書きやすくなります。
>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/27ce63f8-ec22-d89a-fd7a-cccbd53cb445.png)
><a href="https://qiita.com/sasakiki/items/aa2c3b26d9eaab428a43">Reactでredux-sagaで非同期通信 〜redux-thunkとの比較も〜</a>

#実装
カウントボタンを用意し、`Add 1`ボタンを押したときの挙動を確かめました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/57108f75-0778-1dc0-7cd9-fb7ebd9dd91c.png" width="500">

##Viewコンポーネントの作成
結果値、`Add 1`ボタン、`Minus 1`ボタンが縦に並ぶような画面を作成します。
`mapToProps`の実装がめんどくさいイメージがあったのですが、`useSelector`や`useDispatch`という便利なHooksがあることを知り、これらを使ってみました(とても簡単にかけた...)。

```jsx:sagas-example.component.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Card from '../card/card.component';

export const SagasExample = () => {
  const value = useSelector((state) => state.app.value);
  const dispatch = useDispatch();

  return (
    <Card>
      {value}
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Add 1</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Minus 1</button>
    </Card>
  );
};
```

##Middlewareの登録
まず、`store.js`で`redux-saga`からインポートした`createSagaMiddleware`で`sagaMiddleware`を作成し、`middleware`に登録します(ActionとStateのログを監視するために`redux-logger`も登録しています)。

`sagaMiddleware.run(incrementSaga)`で後に作成する`incrementSaga`を実行します。

```js:store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import logger from 'redux-logger';

import rootReducer from './root-reducer';

import { incrementSaga } from './app.saga';

const sagaMiddleware = createSagaMiddleware();

const middlewares = [logger, sagaMiddleware];

export const store = createStore(rootReducer, applyMiddleware(...middlewares));

sagaMiddleware.run(incrementSaga);

export default store;
```

また、`rootReducer`の中身と`appReducer`は以下のようになっています。

```js:root-reducer.js
import { combineReducers } from 'redux';

import appReducer from './app.reducer';

const rootReducer = combineReducers({
  app: appReducer
});

export default rootReducer;
```

```js:app.reducer.js
const INITIAL_STATE = {
  value: 0
};

const appReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case 'INCREMENT_FROM_SAGA':
      return {
        ...state,
        value: state.value + 1
      };
    case 'DECREMENT':
      return {
        ...state,
        value: state.value - 1
      };
    default:
      return state;
  }
};

export default appReducer;
```

##TaskとTask Watcherの作成
Taskとして、3000 ms後に`put({ type: 'INCREMENT_FROM_SAGA' })`を実行(putはdispatchと同じ)する`onIncrement()`作成し、Task Watcherとして、`incrementSaga()`を作成します。

```js:app.saga.js
import { takeEvery, delay, put } from 'redux-saga/effects';

// Task
export function* onIncrement() {
  yield console.log('I am incremented');
  yield delay(3000);
  yield put({ type: 'INCREMENT_FROM_SAGA' });
}

// Task Watcher
export function* incrementSaga() {
  yield takeEvery('INCREMENT', onIncrement);
}
```

###takeEvery
`takeEvery()`で`Add 1`ボタンを押したときにdispatchされるAction('INCREMENT')を監視し、Taskとなる`onIncrement()`を実行します。
`takeEvery()`では監視しているActionが実行されるたびに、Taskを実行します。
そのため、以下のように`INCREMENT`と`INCREMENT_FROM_SAGA`が同じ回数実行されます。
![スクリーンショット 2021-11-07 14.42.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/00318272-9125-347a-2534-0a8809001d60.png)

###takeLatest
`incrementSaga`を`yield takeLatest('INCREMENT', onIncrement)`に書き換えて`Add 1`ボタンを2回押すと、`INCREMENT_FROM_SAGA`は一回しか実行されません。
このように、`takeLatest()`では現在実行中のTaskのみを取得し、その前に実行していたTaskをキャンセルします。
![スクリーンショット 2021-11-07 14.47.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/858861a4-c199-317b-fc90-2d8e0c3d8148.png)

#参考資料

https://www.udemy.com/share/101WH43@3mnMMn6daqhH4BxIUU0SM-rJBY0M0EkpzVBG7_AaTO7ZPI5myW8qESSIzTgI6CHv/

https://qiita.com/sasakiki/items/aa2c3b26d9eaab428a43

https://dev.classmethod.jp/articles/redux-saga-cheatsheet/

https://github.com/redux-saga/redux-saga/blob/master/README_ja.md

https://qiita.com/macotok/items/ec5460ac17f5a20c4735

https://qiita.com/r-nouchi/items/8a0bbb078114959c3a02

https://qiita.com/t_okkan/items/63ea6128c1cfb8cfbdda