Reactにredux-sagaを導入

# はじめに
Reactにredux-sagaをなるべく管理の仕組みを簡単に導入したい、、、という自分メモです。
説明はコメントを読んでもらえればと。
作った人にしかわからないメモかもしれませんが、、、

# 内容修正
 - 2022/01/16
   - 全体的に、微調整しています。
   - Gitのリンク先の内容も修正内容にしたがって変更しています
   - 比較処理が間違っている箇所がありましたので、修正しています
   - 実際に追加方法に従って別名の同じ型のものを増やしてみましたが、両方とも同じactionで認識されてましたので修正しています
   - ファイルの記載順番を変更しています(個人的に見辛かったため)

# フォルダ構成

```
src
 ├ index.js
 ├ App.js
 ├ action
 │  └ temp.js  
 ├ reducer
 │  ├ index.js 
 │  └ temp.js
 ├ saga
 │  ├ index.js 
 │  └ temp.js
 └store
    └ index.js
```

## フォルダ＆ファイル構成のざっくり説明
 - store/reducer/saga の index.js については、仕組みを管理するファイル
 - action/reducer/saga の temp.jsについては、仕組みに紐づいているファイル
 - App.jsについては、ドライバの役割

## パッケージインストール
```shell
yarn add react-redux@7.2.6
yarn add redux-saga@1.1.3
```

# ファイル
## ダウンロード
[こちら](https://github.com/naozo-se/test-redux-saga)からお願いします
※定義追加方法に従って、ファイルが追加されたものになっています

## 仕組みのファイル
### store
```javascript:store/index.js
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "../reducer/index";
import rootSaga from "../saga/index";

// reducerとsagaの紐づけ
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);

export default store;
```

### action
```javascript:action/temp.js
// actionのベース定義名
export const actionBaseNames = "temp";

// actionの名称定義
export const actionNames = {
  ADD_DATA: `${actionBaseNames}_ADD_DATA`,
  CLEAR_DATA: `${actionBaseNames}_CLEAR_DATA`,
  GET_DATA: `${actionBaseNames}_GET_DATA`,
};

// action処理の定義
export const actions = {
  // データの追加(引数が必要な場合は設定)
  [actionNames.ADD_DATA]: (payload) => {
    return { type: actionNames.ADD_DATA, payload };
  },
  // データクリア
  [actionNames.CLEAR_DATA]: () => {
    return { type: actionNames.CLEAR_DATA };
  },
  // 条件等必要な場合は、引数として追加
  [actionNames.GET_DATA]: () => {
    return { type: actionNames.GET_DATA };
  },
};
```

### reducer
```javascript:reducer/index.js
import { combineReducers } from "redux";
import { actionBaseNames as temp } from "../action/temp";
import tempReducer from "./temp";

// combineReducersでreducerをまとめる
const rootReducers = combineReducers({
  // 外で定義しているreducerを追加する
  [temp]: tempReducer,
});

export default rootReducers;
```

```javascript:reducer/temp.js
import { actionNames } from "../action/temp";

// reducerのデータ定義
const initialState = {
  data: [],
};

// reducerの関数定義
const tempReducer = (state = initialState, action) => {
  switch (action.type) {
    case actionNames.ADD_DATA:
      // データの追加
      return { ...state, data: state.data.concat(action.payload) };
    case actionNames.CLEAR_DATA:
      // データのクリア
      return { ...state, data: [] };
    default:
      return state;
  }
};

export default tempReducer;
```

### saga
```javascript:saga/index.js
import { all, fork } from "redux-saga/effects";
import tempSaga from "./temp";

// redux-sagaの対象にするsagaを定義
export default function* rootSaga() {
  yield all([
    // forkでsagaを追加
    fork(tempSaga),
  ]);
}
```

```javascript:saga/temp.js
import { takeEvery, call, put } from "redux-saga/effects";
import { actions, actionNames } from "../action/temp";

// 監視対象のアクションと実行する処理を定義(reducerのtypeにおけるcase対象外のもの)
export default function* tempSaga() {
  // データのFetch処理を監視対象として追加
  yield takeEvery(actionNames.GET_DATA, fetchDataSaga);
  // 他にも監視対象があれば、 yield ~ () で追加する
}

// Fetch処理
function* fetchDataSaga() {
  try {
    // Fetch処理を追加
    const tempData = yield call(fetchData);
    // Fetchしたデータを追加する
    yield put(actions[actionNames.ADD_DATA](tempData));
  } catch (e) {
    // エラー処理が必要な場合はアクションを追加
  }
}

// Fetch処理(リクエスト部分)
// [{ "id": "id_value1", "title": "title_value1" },{ "id": "id_value2", "title": "title_value2" }, ...]
// が返ってくる想定
const fetchData = () => {
  return fetch("http://localhost:8000/testdata/temp").then((response) =>
    response.json()
  );
};
```

## 使い方(ページのコンポーネントのファイルからの実行方法)

```react:index.js
import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";

import App from "./App";
import store from "./store/index";

render(
  <React.StrictMode>
    {/* storeとして設定 */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);
```

```react:App.js
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { v1 as uuidv1 } from "uuid";
import {
  actions as tempActions,
  actionNames as tempActionNames,
  actionBaseNames as tempBaseName,
} from "./action/temp";

const App = () => {
  // 入力内容設定のstate
  const [title, setTitle] = useState("");
  // 実行dispatch
  const dispatch = useDispatch();
  // reducerのデータ取得(リアルタイム)
  const tempData = useSelector((state) => state[tempBaseName].data);
  // 追加ボタン
  const addClickHandler = () => {
    const id = uuidv1();
    if (title) {
      // 追加のアクションを実行
      dispatch(tempActions[tempActionNames.ADD_DATA]({ title, id }));
      setTitle("");
    }
  };
  // データ取得ボタン
  const getClickHandler = () => {
    // データ取得のアクションを実行
    dispatch(tempActions[tempActionNames.GET_DATA]());
  };
  // データクリアボタン
  const clearClickHandler = () => {
    // データクリアのアクションを実行
    dispatch(tempActions[tempActionNames.CLEAR_DATA]());
    setTitle("");
  };

  return (
    <>
      <h1>temp</h1>
      <label htmlFor="title">Title</label>
      <input
        type="text"
        id="title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <button onClick={addClickHandler}>ADD</button>
      <button onClick={getClickHandler}>GET</button>
      <button onClick={clearClickHandler}>CLEAR</button>
      <ul>
        {/* データの出力 */}
        {tempData.length > 0
          ? tempData.map((item, idx) => (
              <li key={idx}>
                {item.title} / {item.id}
              </li>
            ))
          : null}
      </ul>
    </>
  );
};

export default App;
```

# 定義追加方法
1. action の定義ファイルを追加
1. reducer の定義ファイルを追加
1. reducer に追加したファイルを ```reducer/index.js```に追加定義
1. saga の定義ファイルを追加
1. saga に追加したファイルを ```saga/index.js```に追加定義
<br/>
※action/reducer/sagaにあるtemp.jsのようなファイルを追加して、indexに定義を追加したら良い


# さいごに
しっくりくるものを調べきれなかったので書きました。
色々いじってみると、結構動かないところ見つかっています、、
action定義の名称まわりとかはもっと良い書き方がありそうな感じはしていますが、、、