見出し画像

Reactアプリへの Reduxの導入手順

この記事では、ReduxをReactアプリに導入する手順を書いていきます。
コード自体はTypescriptですが、Javascriptでも同じような手順でできます。

注: 勉強メモ程度に書いているので、説明が結構雑です。
記事内のコードは公開していないインターフェースなども含んでいるので、例程度に見てください。

1. 利用するパッケージのインストール

まずは、redux周りのパッケージをインストールしています。今回は、Reduxの中で非同期な処理を行うことを想定して、redux-thunkというパッケージもインストールします。redux-thunkを使うことで、axiosを使ったwebAPIとの通信など非同期な処理を行うことができるようになります。(ただし、今回の記事の中では非同期通信は行わない。)

(ターミナル)
yarn add react-redux @types/react-redux
yarn add redux
yarn add redux-thunk // 非同期な処理を行いたい場合のみ

2. Redux用のフォルダ整理

コードを書いていく前に、redux周りのコードを入れていくフォルダ構成を作っていきます。srcディレクトリの中に、stateというフォルダを作成して、その中にaction-types, actions, action-creators, reducersという名前のフォルダを作成して、下の図のようなフォルダ構成にしてください。

src/
    └ state/
              |- action-types/
              |- actions/
              |- action-creators/
             └- reducers/

action-types:      アクションのtypeを定義するファイルを入れるフォルダ
actions:               アクションのinterfaceを定義するファイルを入れる
action-creators: action creatorのinterfaceを定義するファイルを入れる
reducer:              reducerを定義下ファイルを入れるフォルダ

3. アクションタイプの定義

フォルダを作成したら、action-typesの中にindex.tsファイルを作成して、後で作るactionが持つ、type(string)をenumで定義していきます。アクションタイプを定義することで、スペルミスを減らしたり、エディタの予測変換を効かせるようになります。

(action-types/index.ts)
export enum ActionTypes {
 SET_MENU = 'set_menu',
 SET_INSTRUCTIONS = 'set_instructions',
 PUSH_SCORE = 'push_score',
}

4. アクションインターフェースの定義

後に作成するActionCreatorが返すオブジェクトのtypeとpayloadの型をインターフェースで定義していきます。actionsディレクトリの中にindex.tsファイルを作成してinterfaceを書いていきます。

(actions/index.ts)
import { Instruction } from '../../components/Training/Instructions';
import { ActionTypes } from '../action-types';
import Menu from '../menu';

export interface SetMenuAction {
 type: ActionTypes.SET_MENU;
 payload: Menu;
}

export interface SetInstructionsAction {
 type: ActionTypes.SET_INSTRUCTIONS;
 payload: Instruction[];
}

export interface PushScoreAction {
 type: ActionTypes.PUSH_SCORE;
 payload: number;
}

export type Action = SetMenuAction | SetInstructionsAction | PushScoreAction;


一番下でActionをエクスポート。

ちなみに、Menuというinterfaceは、以下のようになっています。

export default interface Menu {
 title: string;
 timeLimit: number;
 numOfInstructions: number;
}

5. Reducerの作成

次は、reduerの方を作っていきます。state管理をしていて、あとで作るaction creatorから受け取ったpayloadを元にstateを更新する役割です。

reducersディレクトリの中に、trainingReducer.tsというファイルを作成します。

まずは、stateのインターフェースとreducerに渡す初期値の定義をします。

(reducers/trainingReducers)
import { Instruction } from '../../components/Training/Instructions';
import { ActionTypes } from '../action-types';
import { Action } from '../actions';
import Menu from '../menu';

interface TrainingState {
 menu: Menu;
 instructions: Instruction[];
 scores: number[];
}

const initialState: TrainingState = {
 menu: {
   title: 'Nothing',
   timeLimit: 0,
   numOfInstructions: 0,
 },
 instructions: [],
 scores: [],
};

次にreducerを作成

import { Instruction } from '../../components/Training/Instructions';
import { ActionTypes } from '../action-types';
import { Action } from '../actions';
import Menu from '../menu';

interface TrainingState {
 menu: Menu;
 instructions: Instruction[];
 scores: number[];
}

const initialState: TrainingState = {
 menu: {
   title: 'Nothing',
   timeLimit: 0,
   numOfInstructions: 0,
 },
 instructions: [],
 scores: [],
};

// ↓new
const reducer = (state: TrainingState = initialState, action: Action): TrainingState => {
 switch (action.type) {
   case ActionTypes.SET_MENU:
     return { ...state, menu: action.payload };
   case ActionTypes.SET_INSTRUCTIONS:
     return { ...state, instructions: action.payload };
   case ActionTypes.PUSH_SCORE:
     let newScores = state.scores;
     newScores.push(action.payload)
     return { ...state, scores: newScores };
   default:
     return state;
 }
};

export default reducer;

TrainingReducerを作り終わったので、次はindex.tsファイルをreducersフォルダに作成して、他のreducerと統合します。(今回は他のreducrはないですが、ある場合はここで統合する。)

(reducers/index.ts)
import { combineReducers } from "redux";
import cellsReducer from './cellsReducer';
import bundlesReducer from './bundlesReducer';

const reducers = combineReducers({
 cells: cellsReducer,
 bundles: bundlesReducer,
});

export default reducers;
export type RootState = ReturnType<typeof reducers>;

最後に、RootStateをエクスポートしているのは、React側でreducerで管理しているstateの型を確認できるようにするためです。reducerの作成が終わったので、次はaction creatorの方を作っていきます。

6. action creatorsの作成

action creatorはreducerで管理しているデータを更新するときに使うデータと、なんのデータを更新するのかを指定するのが役割です。なんのデータを更新するのかを表すtypeと、更新時に使うデータを積んだpayloadを返す関数を作っていきます。

action-creatorsフォルダの中にindex.tsを新規作成してください。

import { Instruction } from '../../components/Training/Instructions';
import { ActionTypes } from '../action-types';
import {
 SetMenuAction,
 SetInstructionsAction,
 PushScoreAction,
} from '../actions';
import Menu from '../menu';

export const setMenu = (menu: Menu): SetMenuAction => {
 return {
   type: ActionTypes.SET_MENU,
   payload: menu,
 };
};

export const setInstructions = (
 instructions: Instruction[]
): SetInstructionsAction => {
 return {
   type: ActionTypes.SET_INSTRUCTIONS,
   payload: instructions,
 };
};

export const pushScore = (score: number): PushScoreAction => {
 return {
   type: ActionTypes.PUSH_SCORE,
   payload: score,
 };
};

これでaction creatorの方も終わりです。

7. storeの作成とreactにreduxを積む作業

stateを管理するstoreを作成していく。stateフォルダの下にstore.tsを作成して、以下のようにしてください。

import { createStore } from "redux";
import reducers from "./reducers";
import reducer from "./reducers/trainingReducer";

export const store = createStore(reducer);

redux-thunkを使う場合は下のようにしてください。

import { createStore } from "redux";
import reducers from "./reducers";

export const store = createStore(reducers);

storeを作れたので、reactの方にstoreを積んでいきます。reduxを使いたいところ全てのJSXを囲うように、Providerを設置します。

import { Provider } from 'react-redux';

const App = () => {

 return (
   <Provider store={store}>
     <div>
       <reduxを使いたいところ/>
     </div>
   </Provider>
 );
};

最後に、stateフォルダないのコードを呼び出しやすくするため、ちょっとした作業をします。

stateフォルダにindex.tsファイルを作成して、以下のようにしてください。

export * from './store';
export * from './reducers';
export * from './cell';
export * as actionCreators from './action-creators';

これでredux側の準備は全部終わったので、次はreactの方からreduxを使うコードを書いていきます。

8. カスタムフックの作成

reactの方でreduxを使いやすくするため、actionを発生させるuseActionsフックと、reduxのstateにアクセスするuseTypedSelectorフックを作ります。

まずは、useActionsから。
srcフォルダの下に、hooksという名前のフォルダを作り、その中にuseActions.tsという名前のファイルを作成してください。

import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../state';

export const useActions = () => {
 const dispatch = useDispatch();

 return bindActionCreators(actionCreators, dispatch);
};

reactの中では、useActionsの中に格納されているaction creatorを呼び出してactionを発生させます。

では、useTypedSelectorの方も作っていきます。
hooksディレクトリの中に、useTypedSelector.tsというファイルを作成して以下のように書いてください。

import { useSelector, TypedUseSelectorHook } from "react-redux";
import { RootState } from "../state";

export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

react側ではこれを使って、reduxのstateにアクセスします。

導入手順としては以上で終了です。使用例を最後にのっけておきます。

9. 使用例

Trainingというreactコンポーネントで使用。

import { useActions } from '../../hooks/useActions';
import { useTypedSelector } from '../../hooks/useTypedSelector';

const Training = () => {
 // get actionCreators and redux state
 const { setMenu, setInstructions,pushScore } = useActions();
 const { menu, instructions, scores } = useTypedSelector((state) => {
   return {
     menu: state.training.menu,
     instructions: state.training.instructions,
     scores: state.training.scores,
   }
 });

 // setup
 useEffect(() => {
   setMenu({
     title: 'Test Menu',
     timeLimit: 100000,
     numOfInstructions: 10,
   });
 }, []);
 
 return (
    <h1>{menu.title}</h1>
 );
}

自分のコードの場合、下のように表示されます。

画像1


この記事が気に入ったらサポートをしてみませんか?