Redux入門

こんにちは、今回はReactのReduxについてまとめたいと思います。
# はじめに
この記事はこちらの動画を参考にしています。
かなり分かりやすく、ハンズオンでReduxを使用したアプリの作成も出来て、更にYouTubeなので無料で見れます。お時間ある方はこちらをどうぞ。
https://www.youtube.com/playlist?list=PLX8Rsrpnn3IWavNOj3n4Vypzwb3q1RXhr

# 目次
[1.はじめる](#1-はじめる)
[2.ファイル構成](#2-ファイル構成)
[3.storeを書く](#3-storeを書く)
[4.型の設定を書く](#4-型の設定を書く)
[5.値を変更する](#5-値を変更する)
[6.storeの値を取得する](#6-storeの値を取得する)
[7.APIを用いて値を変更する](#7-apiを用いて値を変更する)

# 1. はじめる
:::note info
Reduxとは？
・React全体で変数の値を管理したいときに便利なツール
・Propsなどで値のバケツリレーをしなくて済む
:::

### ■使用技術
・React
・TypeScript

### ■環境構築

```tex:コマンドプロンプトorターミナル
//Reactプロジェクトの作成
npx create-react-app プロジェクト名 --template typescript

//redux
npm i react-redux
npm i redux

//reduxでactionsを使用する際に必要
npm i redux-actions

//reduxの中で非同期処理
npm i redux-thunk

//型を使う時に使用
npm install @reduxjs/toolkit
```

# 2. ファイル構成
store + 項目ごとにフォルダを作成します。
今回はユーザ情報とアイテム情報を管理したいと思います。

```tex:フォルダの作成
src
└ reducks
　├ store
  ├ users
  └ items
```

```tex:src > reducks
store
├ initialState.ts :初期値の設定
└ store.ts　　　   :値を持っている(倉庫)

users(項目名)
 ├ actions.ts　　 :指示の内容を書く
 ├ operations.ts  :APIを使う時等はActionsのメソッドはここを通して呼ぶ
 ├ reducers.ts　  :Actionsからの指示で実際に値の変更を行う
 ├ selectors.ts   :stateの値を取得
 └ types.ts　　　  :型の設定を記載

items(項目名)
 ├ actions.ts　　 
 ├ operations.ts
 ├ reducers.ts
 ├ selectors.ts
 └ types.ts
```

それぞれの役割(処理の流れ)はこのような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/8fa69438-1956-3f08-95ef-7154e4b747f0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/ebe62708-c91f-ed02-9376-09bb325e8337.png)


# 3. storeを書く
### ■initialState.tsを書く
ではまず使う変数を設定したいと思います。
initialState.tsでは変数の初期値を設定します。今回は一旦空にしておきましょう。

```typescript:store>initialState.ts
const initialState = {
  users: {
    //ログインしているユーザ名
    userName: "",
  },
  items: {
    //管理したいアイテム名
    itemName:"",
    //管理したいアイテムの画像
    itemImage:""
  },
};

export default initialState;
```

### ■store.tsを書く
store.tsではRedux全体の設定のようなことをしていきます。
Reducerをインポートする際にエラーになるかと思いますが、ここは後ほど書いていくので一旦OKです。

```typescript:store > store.ts
//Reduxから使用するreduxモジュールのimport
import { createStore as reduxCreateStore, combineReducers } from "redux";
//reducersのインポート
import { UserReducer } from "../users/reducers";
import { ItemsReducer } from "../items/reducers";

//非同期処理の為にredux-thunk使用
import thunk from "redux-thunk";

/**
 * stateを管理の倉庫の設定.
 */
export default function createStore() {
  return reduxCreateStore(
    //分けて作成している変数達を1つの倉庫にまとめてResucerを振る
    combineReducers({
      users: UserReducer,
      items: ItemsReducer,
    }),
    //他に使いたいReduxの機能があればここで宣言。()の中に機能名を入れる
    applyMiddleware(thunk)
  );
}
```

# 4. 型の設定を書く
ここはJavascriptであれば不要かと思います。

```typescript: users > types.ts
export type userType = {
  users: {
    userName: string;
  };
};
```
```typescript: items > types.ts
export type itemType = {
  items: {
    itemName: string;
    itemImage: string;
  };
};
```

# 5. 値を変更する
ではここから早速、処理を書いていきます。
### ■Actions→Reducers
名前を入れてサインインボタンを押したらstoreのuserNameに名前が入るガバガバサインイン機能を作成します。

【復習】処理の流れ：今回はAPIを使わないので下の流れです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/8fa69438-1956-3f08-95ef-7154e4b747f0.png)

### ■Actionsを書く

```typescript:users > actions.ts
//指示名を定数に入れてあげる(Reducerで使う際に依頼名を判断するため)
export const SIGN_IN = "SIGN_IN";

/**
 * userNameに受け取った名前をいれるというメソッド.
 */
export const signInAction = (userState: {
  userName: string;
}) => {
  return {
    //指示名
    type: "SIGN_IN",
    //実際どの値をどう書き換えるか
    payload: {
      userName: userState.userName,
    },
  };
};
```
引数の形はuserState: { userName: string; } の形です。
もし引数を使わない場合は書かなくてOKです。

### ■Reducersを書く
では上で作成したActionsを発火させるメソッドを書いていきます。
の前に一旦型を追加しましょう。

```typescript:users > types.ts
export type userType = {
  users: {
    userName: string;
  };
};

//Actionsを発火させる際に使用
export type userActionType = {
  type: "SIGN_IN";
  payload: { userName: string; };
};
```

では上で設定した型等も用いてReducerを書きます。
ちょっと書き方がややこしいですがとりあえずこの形で書く、という事が分かれば良いと思います。

```typescript:users > reducers.ts
//ActionsフォルダのActionsを全てインポート
import * as Actions from "./actions";
//初期値のインポート
import initialState from "../store/initialState";
//型
import { PayloadAction } from "@reduxjs/toolkit";
import { userActionType } from "./types";

/**
 * ユーザ情報の変更.
 */
export const UserReducer = (
  state = initialState.users,
  action: PayloadAction<userActionType>
) => {
  switch (action.type) {
    //Actionsで作成した指示のどれを使うか
    /**
     * サインイン.
     */
    case Actions.SIGN_IN:
      return {
        //state(初期値)とaction.payload(変更後の値)をマージする
        ...state,
        ...action.payload,
      };
    /**
     * どの指示名にも当てはまらなければそのまま値を返す.
     */
    default:
      return state;
  }
};

```

[先程](#3-storeを書く)store > store.tsにて該当のReducerを振っていったと思いますが、ここで設定したReducerを振っていたという訳です。そのため、こちらを先に書いた方がスムーズかもしれません。

ではここまでで設定したサインイン機能をページに実装していきます。

```react:SignIn.tsx
import { useState, useCallback } from "react";
import { useDispatch } from "react-redux";
import { signInAction } from "../reducks/users/actions";

export const SignIn = () => {

 //Actionsのメソッドを使う際に必要
  const dispatch = useDispatch();

  //ログインユーザ名
  const [name, setName] = useState("");

  /**
   * 名前の入力.
   */
  const inputName = useCallback((e) => {
    setName(e.target.value);
  }, []);

  return (
    <>
      <input type="text" onChange={(e)=>inputName(e)}/>
      <button onClick={ dispatch(signInAction( {userName:name} )) }>サインイン</button>
    </>
  );
};

export default SignIn;
```

画面の様子:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/9e700338-67e0-13bf-e6da-82b92c274910.png)
これで「サインイン」ボタンをクリックすると、Reducersの「signIn」が発火→Actionsが発火してstoreの変数(state)であるuserNameの値が入力した名前に書き換わるという事です。すごーいややこしいー

# 6. storeの値を取得する
では次に先ほど書き換えた値を取得してみます。
【復習】処理の流れ：直接storeからでなく、selectorsを通して取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/ebe62708-c91f-ed02-9376-09bb325e8337.png)

### ■Selectorsを書く
```typescript:users > selectors.ts
import { createSelector } from "reselect";
import { userType } from "./types";

//ユーザ情報全てをuserSelectorに代入
const userSelector = (state: userType ) => state.users;

/**
 * ユーザ名取得.
 */
export const getUserName = createSelector(
  [userSelector],
  (state) => state.userName
);
```
もしユーザの名前を使用したい際は、この「getUserName」を通して取得することになります。今回はHome.tsxというページを作成して見てみましょう。

```react:Home.tsx
import { useSelector } from "react-redux";
import { getUserName } from "../reducks/users/selecoters";
import { userType } from "../reducks/users/type";

export const Home = () => {
  //値の呼び出し
  const selector = useSelector((state: userType ) => state);
  console.log("ログインしているユーザ:" + getUserName(selector));

  const userName = getUserName(selector);

  return (<>{ userName }</>);
};

export default Home;
```
出た
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/d56a6904-1033-bfa6-946d-e1044fddbe7d.png)
ポイントは型(userType)の付け所です。つけないとerrorになるので注意。

# 7. APIを用いて値を変更する

さあ、最もややこしいと感じているところです。
今回は練習によく使っているポケモンAPIを使用し、storeのitemsに情報を入れられるようにしたいと思います。

:::note info
operationsとreucersの使い分け
reducersではawaitや.then等の「待つ処理」が出来ないため、APIからのデータを利用してstateを書き換える際はawait出来るoperationsを通す必要があります。
:::

operationsは待ての出来るワンチャン(?)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/fa0ff1fc-ed5c-0fab-9fff-4b0ab1b1888f.png)


【復習】処理の流れ：今回はAPIを使用するので上の流れです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/8fa69438-1956-3f08-95ef-7154e4b747f0.png)

### ■Actionsを書いていく
メソッド名GETにしちゃいましたが書き換える役割なのでEDITとかのが良かったかな。。

```typescript:items > actions.ts
export const GET_POKE = "GET_POKE";

/**
 * アイテム情報を書き換える.
 * @param itemState - 書き換えたい値
 */
export const getPokeAction = (itemState: { name: string; image: string }) => {
  return {
    type: "GET_POKE",
    payload: {
      itemName: itemState.name,
      itemImage: itemState.image,
    },
  };
};
```

### ■型の追加

```typescript:items > types.ts
export type itemType = {
  items: {
    itemName: string;
    itemImage: string;
  };
};

export type itemActionType = {
  type: "GET_POKE";
  payload: {
    itemName: string;
    itemImage: string;
  };
};
```
### ■Reducersを書いていく

```typescript:items > reducers.ts
//ActionsフォルダのActionsを全てインポート
import * as Actions from "./actions";
//初期値のインポート
import initialState from "../store/initialState";
//型
import { PayloadAction } from "@reduxjs/toolkit";
import { itemActionType } from "./types";

export const ItemReducer = (
  state = initialState.users,
  action: PayloadAction<itemActionType>
) => {
  switch (action.type) {
    //Actionsで作成した指示のどれを使うか
    /**
     * ポケモン情報取得.
     */
    case Actions.GET_POKE:
      return {
        //state(初期値)とaction.payload(変更後の値)をマージする
        ...state,
        ...action.payload,
      };
    /**
     * 当てはまらなければ.
     */
    default:
      return state;
  }
};
```

### ■Operationsを書いていく
さて、本命です。まずは書き方から。

メソッドの中でreturn async (dispatch: Dispatch<unknown>) => {}をして、{}でActionsのメソッドを呼んであげる形になります。
```tex
export const Hogehoge = () => {
  return async (dispatch: Dispatch<unknown>) => {
    APIでデータを取得したりなんだり…
    dispatch(HogeAction({APIで取得した、stateを書き換えるデータを渡してあげる}));
  };
};
```
実際に書いていきます。
今回APIはaxiosで呼んでいるので同じようにする場合はnpm i axiosをお忘れなく。

```typescript:items > operations.ts
import { Dispatch } from "react";
import axios from "axios";
import { getPokeAction } from "./actions";

/**
 * ポケモンの情報を取得してActionsにstateを書き換えるためのデータを渡す.
 */
export const getPoke = (id: number) => {
  return async (dispatch: Dispatch<unknown>) => {
    const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${id}`);
    dispatch(
      getPokeAction({
        name: response.data.name,//名前
        image: response.data.sprites.front_default,//画像パス
      })
    );
  };
};
```

これで実装が完了しました。
一応試すために、stateの情報を取得出来るメソッドも実装しておきましょう。

### ■Selecotersを書く

```typescript:items > selecoters.ts
import { createSelector } from "reselect";
import { itemType } from "./types";

//アイテムの全情報取得
const itemSelector = (state: itemType) => state.items;

/**
 * アイテムの名前取得.
 */
export const getItemName = createSelector(
  [itemSelector],
  (state) => state.itemName
);

/**
 * アイテムの画像データ取得.
 */
export const getItemImage = createSelector(
  [itemSelector],
  (state) => state.itemImage
);
```

### ■実装する
Home.tsxに作成したメソッドを実装していきます。
先程作成したサインインメソッドは一旦削除し、取得したいポケモンのIDを入れる→「Actions」ボタンで情報取得、「Selectors」ボタンで情報を表示するようにしてみました。

今は情報が空なので表示されていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/67af550a-be6a-a4fe-3074-e708fbf87a59.png)

```react
import { useState, useCallback} from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPoke } from "../reducks/items/operations";
import { getItemImage, getItemName } from "../reducks/items/selecoters";
import { itemType } from "../reducks/items/types";

export const Home = () => {
  //Actionsのメソッドを使う際に必要
  const dispatch = useDispatch();

  //取得したいポケモンID
  const [id, setID] = useState<number>(0);

  /**
   * IDの入力.
   */
  const inputID = useCallback((e) => {
    setID(e.target.value);
  }, []);

  //selectorの使用
  const selector = useSelector((state: itemType) => state);
  //ポケモンの情報
  const [pokeName, setPokeName] = useState("");
  const [pokeImage, setPokeImage] = useState("");

  /**
   * stateの情報をこのページの変数に入れる.
   */
  const showItem = useCallback(() => {
    setPokeName(getItemName(selector));
    setPokeImage(getItemImage(selector));
  }, [selector]);

  return (
    <>
      <div>
        <input type="text" onChange={(e) => inputID(e)} />
      </div>

      <button onClick={() => dispatch(getPoke(id))}>Actions!</button>
      <button onClick={showItem}>Selectors!</button>
      <div>名前:{pokeName}</div>
      <div>
        <img src={pokeImage} alt="画像" />
      </div>
    </>
  );
};

export default Home;
```

### ■使ってみる
IDを入力→Actionsボタン→Selectorsボタンで無事に表示されました。
可愛いですね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/dffdbe4f-6745-0153-9591-a4856f567c1a.png)

IDを変えるときちんと表示も変わりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2365717/278c0b69-4850-ab97-70c2-1c0c74f70310.png)

ちなみにリロードすると値は消えるようです。

# 8. まとめ
今回はReduxについてまとめました。Reduxには他にもルーティングの管理をしてくれる機能等色々あるみたいなので、まだまだ勉強の余地がありそうです。

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

### ■参考資料
・トラハックさんのYouTube:「<a href="https://www.youtube.com/playlist?list=PLX8Rsrpnn3IWavNOj3n4Vypzwb3q1RXhr">日本一わかりやすいReact-Redux入門</a>」
・<a href="https://github.com/deatiger/ec-app-demo">トラハックさんのGitHub</a>
・<a href="https://pokeapi.co/">Poke Api</a>
・<a href="https://taiyosite.com/pokeapi-elementary/">PokeAPIの使い方【初心者向け】</a>