こんにちは、今回は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
■環境構築
//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
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
3. storeを書く
■initialState.tsを書く
ではまず使う変数を設定したいと思います。
initialState.tsでは変数の初期値を設定します。今回は一旦空にしておきましょう。
const initialState = {
users: {
//ログインしているユーザ名
userName: "",
},
items: {
//管理したいアイテム名
itemName:"",
//管理したいアイテムの画像
itemImage:""
},
};
export default initialState;
■store.tsを書く
store.tsではRedux全体の設定のようなことをしていきます。
Reducerをインポートする際にエラーになるかと思いますが、ここは後ほど書いていくので一旦OKです。
//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であれば不要かと思います。
export type userType = {
users: {
userName: string;
};
};
export type itemType = {
items: {
itemName: string;
itemImage: string;
};
};
5. 値を変更する
ではここから早速、処理を書いていきます。
■Actions→Reducers
名前を入れてサインインボタンを押したらstoreのuserNameに名前が入るガバガバサインイン機能を作成します。
【復習】処理の流れ:今回はAPIを使わないので下の流れです。
■Actionsを書く
//指示名を定数に入れてあげる(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を発火させるメソッドを書いていきます。
の前に一旦型を追加しましょう。
export type userType = {
users: {
userName: string;
};
};
//Actionsを発火させる際に使用
export type userActionType = {
type: "SIGN_IN";
payload: { userName: string; };
};
では上で設定した型等も用いてReducerを書きます。
ちょっと書き方がややこしいですがとりあえずこの形で書く、という事が分かれば良いと思います。
//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を振っていたという訳です。そのため、こちらを先に書いた方がスムーズかもしれません。
ではここまでで設定したサインイン機能をページに実装していきます。
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;
画面の様子:
これで「サインイン」ボタンをクリックすると、Reducersの「signIn」が発火→Actionsが発火してstoreの変数(state)であるuserNameの値が入力した名前に書き換わるという事です。すごーいややこしいー
6. storeの値を取得する
では次に先ほど書き換えた値を取得してみます。
【復習】処理の流れ:直接storeからでなく、selectorsを通して取得します。
■Selectorsを書く
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というページを作成して見てみましょう。
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;
出た
ポイントは型(userType)の付け所です。つけないとerrorになるので注意。
7. APIを用いて値を変更する
さあ、最もややこしいと感じているところです。
今回は練習によく使っているポケモンAPIを使用し、storeのitemsに情報を入れられるようにしたいと思います。
operationsとreucersの使い分け
reducersではawaitや.then等の「待つ処理」が出来ないため、APIからのデータを利用してstateを書き換える際はawait出来るoperationsを通す必要があります。
【復習】処理の流れ:今回はAPIを使用するので上の流れです。
■Actionsを書いていく
メソッド名GETにしちゃいましたが書き換える役割なのでEDITとかのが良かったかな。。
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,
},
};
};
■型の追加
export type itemType = {
items: {
itemName: string;
itemImage: string;
};
};
export type itemActionType = {
type: "GET_POKE";
payload: {
itemName: string;
itemImage: string;
};
};
■Reducersを書いていく
//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をお忘れなく。
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を書く
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」ボタンで情報を表示するようにしてみました。
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ボタンで無事に表示されました。
可愛いですね
ちなみにリロードすると値は消えるようです。
8. まとめ
今回はReduxについてまとめました。Reduxには他にもルーティングの管理をしてくれる機能等色々あるみたいなので、まだまだ勉強の余地がありそうです。
ここまでお読みいただきありがとうございました!
■参考資料
・トラハックさんのYouTube:「日本一わかりやすいReact-Redux入門」
・トラハックさんのGitHub
・Poke Api
・PokeAPIの使い方【初心者向け】