Redux Toolkit（チュートリアルアプリ解説）

#はじめに
本稿では`npx create-react-app example-app --template redux`で作成される、redux toolkitチュートリアルアプリケーションのsourceコードに、もともとコメント解説が書いてあるのですが、それをもう少しわかりやすく書き直し、追加のコメントも入れて説明しています。

[コード解説しか必要ないという方はこちら](https://qiita.com/zgoovw_/items/16cc5be86a3fa00eda28#counter%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E5%86%85%E3%81%AEcomponent%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%84%E3%81%8D%E3%81%BE%E3%81%99)

# Reduxツールキットとは
ReduxツールキットはもともとReduxの次の３つの問題を対処するために作られたパッケージです。

- Reduxストアの設定が複雑。
- Reduxを利用するためにはたくさんのパッケージを追加する必要がある。
- Reduxにはコピペで済むようなコードを無駄に書く必要がたくさんある。

###Reduxツールキットが提供するAPI

[ReduxToolkit公式ドキュメントより](https://redux-toolkit.js.org/introduction/getting-started#whats-included)
(Reduxの理解を前提に書かれているのでReduxを知らない方は飛ばしてください。)
> - `configureStore()`: createStoreをラップして、シンプルな構成オプションと優れたデフォルトを提供します。スライスリデューサを自動的に組み合わせたり、提供するReduxミドルウェアを追加したり、デフォルトでredux-thunkを含めたり、Redux DevTools Extensionを使用できるようにしたりすることができます。
- `createReducer()`: アクションタイプのルックアップテーブルをcase reducer関数に与えることができ、switch文を書く必要がありません。さらに、immerライブラリを自動的に使用して、state.todos[3].completed = trueのように、通常のミュータティブコードでよりシンプルなイミュータブルアップデートを書くことができます。
- `createAction()`: 与えられたアクションタイプの文字列に対して、アクション作成関数を生成します。この関数自体にtoString()が定義されているので、型定数の代わりに使うことができます。
- `createSlice()`: リデューサ関数のオブジェクト、スライス名、初期状態の値を受け取り、対応するアクション・クリエータとアクション・タイプを持つスライス・リデューサを自動的に生成する。
- `createAsyncThunk`: アクション タイプの文字列と、promiseを返す関数を受け取り、その約束に基づいて保留/充足/拒否されたアクション タイプをディスパッチするサンクを生成します。
- `createEntityAdapter`: ストア内の正規化されたデータを管理するために、再利用可能なリデューサとセレクタのセットを生成します。
- ReselectライブラリのcreateSelectorユーティリティ: 使いやすさのために再移植したものです。

#Reduxとは

ReduxとはReactアプリケーションの中でstateを見通しよくどこからでも扱えるようにするための仕組みことで、
以下の機能を使用して状態の取得、伝達、保持などを行います。

- store - アプリケーションの全てのstateを保持するオブジェクト。
- action - （削除ボタンがクリックされた）などの情報を持つオブジェクト。
- dispatch - actionをstoreに伝達する。
- reducer - storeから受け取ったActionとstateを元に新しいstateを計算してstoreに返します。

# Reduxツールキットを使用したコード解説




```
npx create-react-app my-app --template redux

// または

npx create-react-app my-app --template redux-typescript
```

上記のコマンドでReactアプリケーションを作成すると、あらかじめRedux Toolkitのチュートリアルアプリが付属されていて、/src/features配下にcouterというフォルダが作られています。

Reduxツールキットを使用する場合ではstoreの中に、ログイン機能周りのstateとreducerを持つloginSlicer、フォロー機能周りのstateとreducerを持つfollowSlicer、いいね機能周りのstateとreducerを持つlikeSliceといった個別のかたまり(sliceと呼ぶ)を作ります。
そして、それぞれの機能を使用する際に必要なsliceを使用する流れになります。

####counterフォルダ内のcomponentを見ていきます。

```js:counterSlice.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchCount } from './counterAPI';

const initialState = {
  value: 0, // stateの初期値
  status: 'idle', // extraReducersのstatusの初期値
};

// 以下の関数はthunkと呼ばれ、非同期のロジックを実行することができます。これは
// `dispatch(incrementAsync(10))`のように通常のアクションのようにdispatchできます。
// これは、`dispatch`関数を第一引数としてthunkを呼び出します。
// 非同期コードを実行したり、他のactionをdispatchすることができます。thunkは
// 一般的には、非同期のリクエストを行うために使用されます。
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount', // 'スライス名/メソッド名'とすることが多い。
  async (amount) => {
    const response = await fetchCount(amount);

    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // reducersフィールドでは、reducerを定義し、関連するactionを生成することができます。
  reducers: {
    increment: (state) => {
      // Redux Toolkitを使うと、Reducerの中に stateを自由に変更するロジックを書くことができます。
      // ですが実際には元のstateオブジェクトを変更しません。
      // ロジック内でstateの変更を検知し、その変更に基づいて全く新しい
      // stateオブジェクトを生成します。
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // ユーザーがアクションを起こした際に受け取った値をpayloadに持っています。
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  // createAsyncThunkを使用する際にextraReducersで
  // pending(loading時),fulfilled(成功時),rejected(失敗時),の3つのケースを指定して
  // 処理を書いていきます。
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});
// 以下のexpotされた各actionをcomponentで使用する際、
// `onClick={() => dispatch(increment())}`のような形で
// dispatchしてstoreに出力します。
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 以下のexportされたselectCountをcomopnentで取得する際、
// `useSelector((state: RootState) => state.counter.value)`のような形で受け取ります。
export const selectCount = (state) => state.counter.value;

export default counterSlice.reducer;
```

storeは/src/app/store.js　のファイルにあります。
Redux Toolkitを使用する場合は、`configureStore`の中にstoreが内包される形になります。

```js:store.js
import { configureStore } from '@reduxjs/toolkit';
// このアプリケーションではsliceは一つしかありませんが、複数ある場合、全てのsliceをstore.jsにインポートします。　
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
// sliceは一つのreducerを持ち、全てのreducerをここでまとめます。
// 左側はreducerを識別するための名称になります。
    counter: counterReducer,
  },
});
```

```js:Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  decrement,
  increment,
  incrementByAmount,
  incrementAsync,
  selectCount,
} from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
　　　　// useSelectorはslice内のstateを参照します。
  const count = useSelector(selectCount);
  // useDispatchはactionをsliceに伝達します。
  const dispatch = useDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  const incrementValue = Number(incrementAmount) || 0;

  return (
    <div>
      <div className={styles.row}>
        <button
          className={styles.button}
          aria-label="Decrement value"
　　　　　　　　　　　　　　　　　　　　// action.typeにdecrementという情報をcounterSliceのreducerに伝達します。
          onClick={() => dispatch(decrement())}
        >
          -
        </button>
        <span className={styles.value}>{count}</span>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          +
        </button>
      </div>
      <div className={styles.row}>
        <input
          className={styles.textbox}
          aria-label="Set increment amount"
          value={incrementAmount}
　　　　　　　　　　　　　　　　　　　　// 計算の必要のない値の一時的な保持なので、reduxを使用せずuseStateを使用している。
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          className={styles.button}
          // action.typeにincrementByAmount、
          // action.paylordにincrementValueという情報を持って、
　　　　　　　　　　　　　　　　　　　　// counterSliceのreducerに伝達します。
          onClick={() => dispatch(incrementByAmount(incrementValue))}
        >
          Add Amount
        </button>
        <button
          className={styles.asyncButton}
          onClick={() => dispatch(incrementAsync(incrementValue))}
        >
          Add Async
        </button>
      </div>
    </div>
  );
}
```


```js:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
// Providerで囲ってstoreを渡すことで、APP内のどこからでもstoreにアクセスすることができます。
    <Provider store={store}> 
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.unregister();
```


```js:counterAPI.js
// counterSlice.jsのincrementAsync()の中で呼び出される
// データの非同期リクエストを模倣したモック関数
export function fetchCount(amount = 1) {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ data: amount }), 500)
  );
}
```