【Redux】Redux Thunkを使ってReduxで非同期処理を扱う方法。

こんにちは。Naotoです。

今回は、Redux Thunkを使ってReduxで非同期処理（その他副作用処理）を扱う方法について説明したいと思います。

※この記事では、TypeScript, Redux Toolkitを使用します。

## 前提知識
* Reactの基礎知識
* Reduxの基礎知識

## 学んだきっかけ

個人開発中のタスク管理アプリの実装中、「初期レンダリング時に、APIを叩いてデータベースからタスクやカテゴリなどの情報を取得し、ReduxのStateに反映させる」といったコードを書いていました。

最初に思いついた方法は、トップページのuseEffect内に非同期処理を直接書き、その後、取得した値をReduxのStateにdispatchする方法です。

コードは以下です。
```jsx
useEffect(() => {
  (async () => {
    // 未完了タスク取得
    const inCompletedTaskItems: TaskItem[] = await taskApi.inCompletedTaskGet(userId);
    // 取得した未完了タスクを未完了タスクStateに反映
    inCompletedTaskItems.forEach((inCompletedTaskItem) =>
      dispatch(inCompletedTaskAdd(inCompletedTaskItem))
    );

    // 完了タスク取得
    const completedTaskItems: TaskItem[] = await taskApi.completedTaskGet(userId);
    // 取得した完了タスクを完了タスクStateに反映
    completedTaskItems.forEach((completedTaskItem) =>
      dispatch(completedTaskAdd(completedTaskItem))
    );

    // カテゴリ取得
    const categories: Category[] = await taskApi.categoryGetAll(userId);
    // 取得したカテゴリをカテゴリStateに反映
    categories.forEach((category) => dispatch(categoryAdd(category)));

    // スケジュール取得
    const schedules: Schedule[] = await taskApi.scheduleGetAll(userId);
    // 取得したスケジュールをスケジュールStateに反映
    schedules.forEach((schedule) => dispatch(scheduleAdd(schedule)));
  })();
}, []);

```

やっていることは単純ですが、ものすごくコードが長くなってしまいます。
そこで、他に何か良い方法が無いか調査したところ、今回紹介する、Redux Thunkに辿り着きました。


## Redux Thunkとは？

Redux Thunkとは、タイトルの通り、Reduxで非同期処理を扱えるようにするものです。

通常、Reduxを使ってアプリケーションの状態管理を行う際、Stateを更新するためには、Reducerに更新処理を記述します。しかし、Reducerは純粋関数でないといけないといったルールがあります。
<br>
※純粋関数　：以下の性質を持つ関数
* returnが引数のみに依存する。
* その関数外に影響を与えない。（参照・変更しない。）

<br>

そのため、「Reducer内でAPIを叩いて、Stateの更新と同時にデータベースを更新する」といったことはできません。

しかしRedux Thunkを使うことで、
非同期処理をはじめとする、DOM操作、コンソールへのログ出力、タイマー処理といった、いわゆる「副作用」をReduxで扱えるようになります。

（Redux Thunkを用いて記述した副作用は、ReducerではなくMiddlewareという部分で扱われます。）

## Redux Thunkを使用したコード

### Redux Thunk関数の定義
基本形は以下になります。

以下、reduxThunkFnが実行されると、payloadとして引数を受け取り、同時にdispatch関数、getState関数を引数とする関数が生成されreturnされます。
そして、returnされる関数内で、副作用の処理を行うことができます。

```tsx
const reduxThunkFn = (payload) => {
    return (dispatch, getState) => {
        副作用処理
    }
}
```

では、今回は、未完了タスクをAPIから取得し、ReduxのStateに反映する場合について説明します。

以下は、未完了タスクのSlicerにRedux Thunk関数を定義した例です。
```tsx
import { TaskItem } from "../@types";
import { Dispatch, createSlice } from "@reduxjs/toolkit";
import taskApi from "../api/task";

// 未完了タスクState////////////////////////////////////////////////////////////////////

// 初期値
const initialState: TaskItem[] = [];

// Slice作成
export const inCompletedTaskItemsSlice = createSlice({
  name: "inCompletedTaskItems",
  initialState,
  reducers: {
    // タスク追加
    inCompletedTaskAdd: (state, action) => {
      state.push(action.payload);
    },
  },
});

//　ここでRedux Thunk関数を定義（TypeScriptを使用しているため、型情報を追記しています。）
const getAllInCompletedTaskItems = (payload: string) => {
  return async (dispatch: Dispatch, getState: () => TaskItem) => {
    // ここで、APIを叩きデータベースから未完了タスクを取得しています。（副作用処理）
    const inCompletedTaskItems: TaskItem[] = await taskApi.inCompletedTaskGet(payload);
    // 引数のdispatch関数を使用し、取得した未完了タスクをStateに反映
    inCompletedTaskItems.forEach((inCompletedTaskItem) =>
      dispatch(inCompletedTaskAdd(inCompletedTaskItem))
    );
  };
};

// Redux Thunk関数をexport
export { getAllInCompletedTaskItems };

export default inCompletedTaskItemsSlice.reducer;
```

上記により、データベースからのデータ取得とReduxのState更新を同時に行える、getAllInCompletedTaskItems関数が完成し、他コンポーネントから使用可能となりました。

同じように、完了タスク、カテゴリー、スケジュールのSliceにもRedux Thunk関数を定義します。（コードは割愛）

### Redux Thunk関数の使用
上記でexportしたそれぞれのRedux Thunk関数をトップページのuseEffect内で呼び出します。

```jsx
useEffect(() => {
  (async () => {
    // APIから未完了タスクを取得＆Stateに反映
    dispatch(getAllInCompletedTaskItems(userId));

    // APIから完了タスクを取得＆Stateに反映
    dispatch(getAllCompletedTaskItems(userId));

    // APIからカテゴリを取得＆Stateに反映
    dispatch(getAllCategories(userId));

    // APIからスケジュールを取得＆Stateに反映
    dispatch(getAllSchedules(userId));
  })();
}, []);

```
コードが冒頭のコードと比べてとても簡潔になりました。


## まとめ

このように、Redux Thunkを使用することで、非同期処理をReduxのアクションとして管理し、コードを整理することができます。

ぜひ、皆さんもRedux Thunkを活用して、Reduxでの非同期処理を効率化してみてください！！！

最後までお読みいただきありがとうございました！

<br>
ps. 週２投稿を目指していますが、最近週１が限界です。。。頑張ります。。。。