1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Redux入門

Last updated at Posted at 2022-04-14

こんにちは、今回はReactのReduxについてまとめたいと思います。

はじめに

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

目次

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

1. はじめる

Reduxとは?
・React全体で変数の値を管理したいときに便利なツール
・Propsなどで値のバケツリレーをしなくて済む

■使用技術

・React
・TypeScript

■環境構築

コマンドプロンプト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 + 項目ごとにフォルダを作成します。
今回はユーザ情報とアイテム情報を管理したいと思います。

フォルダの作成
src
└ reducks
 ├ store
  ├ users
  └ items
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

image.png

3. storeを書く

■initialState.tsを書く

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

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

export default initialState;

■store.tsを書く

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

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であれば不要かと思います。

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

5. 値を変更する

ではここから早速、処理を書いていきます。

■Actions→Reducers

名前を入れてサインインボタンを押したらstoreのuserNameに名前が入るガバガバサインイン機能を作成します。

【復習】処理の流れ:今回はAPIを使わないので下の流れです。
image.png

■Actionsを書く

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を発火させるメソッドを書いていきます。
の前に一旦型を追加しましょう。

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

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

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

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;
  }
};

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

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

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
これで「サインイン」ボタンをクリックすると、Reducersの「signIn」が発火→Actionsが発火してstoreの変数(state)であるuserNameの値が入力した名前に書き換わるという事です。すごーいややこしいー

6. storeの値を取得する

では次に先ほど書き換えた値を取得してみます。
【復習】処理の流れ:直接storeからでなく、selectorsを通して取得します。
image.png

■Selectorsを書く

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というページを作成して見てみましょう。

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
ポイントは型(userType)の付け所です。つけないとerrorになるので注意。

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

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

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

operationsは待ての出来るワンチャン(?)
image.png

【復習】処理の流れ:今回はAPIを使用するので上の流れです。
image.png

■Actionsを書いていく

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

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,
    },
  };
};

■型の追加

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

export type itemActionType = {
  type: "GET_POKE";
  payload: {
    itemName: string;
    itemImage: string;
  };
};

■Reducersを書いていく

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) => {}をして、{}でActionsのメソッドを呼んであげる形になります。

export const Hogehoge = () => {
  return async (dispatch: Dispatch<unknown>) => {
    APIでデータを取得したりなんだり…
    dispatch(HogeAction({APIで取得した、stateを書き換えるデータを渡してあげる}));
  };
};

実際に書いていきます。
今回APIはaxiosで呼んでいるので同じようにする場合はnpm i axiosをお忘れなく。

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を書く

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

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

IDを変えるときちんと表示も変わりました。
image.png

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

8. まとめ

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

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

■参考資料

・トラハックさんのYouTube:「日本一わかりやすいReact-Redux入門
トラハックさんのGitHub
Poke Api
PokeAPIの使い方【初心者向け】

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?