Reduxとは?
Reduxとはグローバルな状態を管理するために使用されるライブラリです。
コンポーネント全体で管理すべき、グローバルな状態がReduxで管理されます。
- ログイン状態
- ローディング状態
グローバルな値を1つのStoreで管理することで、propsのバケツリレーから解放されます。
この記事では、Reduxの基本から状態管理について解説します。
Reduxの原則
Reduxは3つの基本原則で説明されます。
- 単一ソースで全ての値を管理する
- 状態は読み取り専用である
- 状態の更新は純粋関数によって行われる
Reduxでの状態管理と更新の流れ
Reduxでの状態管理と、状態の更新は以下のデータフローによって行われます。
- 入力をトリガーとして、
ActionCreator
が、対応するイベントを示すAction
を作成 - データを管理する
Store
へ、Action
がDispatch
(送信)される -
Action
がReducer
(状態を変更する純粋関数)に渡される -
Reducer
によって生成された新しいState
が保存される - 新しい
State
をView
がレンダリングする
Reduxの導入 ~ 状態管理
Reduxを使用するために、まずはライブラリをインストールします。
1. ライブラリのインストール
yarnを使用している場合
yarn add redux react-redux
npmを使用している場合
npm i redux react-redux
2. Action の定義
次に、Actionを作成します。
対応するイベントを示すAction
を作成を行うActionCreator
を定義します。
// stores/action.ts
export const CounterActions = {
INCREMENT: 'counter/increment',
DECREMENT: 'counter/decrement',
} as const;
type ValueOf<T> = T[keyof T];
export interface CounterAction {
type: ValueOf<typeof CounterActions>;
payload?: unknown;
error?: boolean;
meta?: string;
}
// ActionCreators
export const increment = (): CounterAction => ({
type: CounterActions.INCREMENT,
});
export const decrement = (): CounterAction => ({
type: CounterActions.DECREMENT,
});
Action
Redux
におけるAction
は、アプリケーションの状態を変更するための指示を含むオブジェクトです。
Action
はtype
というプロパティを持ち、このtype
プロパティがアクションの種類を示します。また、必要に応じてpayload
やerror
、meta
といった追加のプロパティを持つことができます。
FSA
FSA(Flux Standard Action)
は、Action
の標準形式を定めた規格です。
FSAに従うアクションオブジェクトは、次のプロパティを持ちます。
-
type
:アクションの種類を示す文字列 -
payload?
:アクションが関与するデータ -
error?
:アクションがエラーであることを示すブール値 -
meta?
:アクションに関するメタデータ
Actionは基本的にはFSAに順守した形で作成するようにしましょう。
3. Reducerの定義
Action
の次は、状態を変更する純粋関数である Reducer
の実装です。
Reducer
は、prevState
とAction
を受け取り、newState
を返す純粋関数です。
Action
に応じた状態の更新処理を実装します。
// stores/reducer.ts
import { Reducer } from 'redux';
import { CounterAction, CounterActions } from './action';
export interface CounterState {
count: number;
}
export const initialState: CounterState = { count: 1 };
export const counterReducer: Reducer<CounterState, CounterAction> = (
state = initialState,
action: CounterAction,
) => {
switch (action.type) {
case CounterActions.INCREMENT:
return {
...state,
count: state.count + 1,
};
case CounterActions.DECREMENT:
return {
...state,
count: state.count - 1,
};
// defaultの定義がないとエラーになるので注意
default:
return state;
}
};
4. ActionをDispatcherに渡す
useDispatch
フックを使用して、Store
にAction
を送信します。
送信されたAction
は、Reducer
によって処理され、状態が更新されます。
5. 状態の取得
useSelector
フックを使用して、Store
から状態を取得します。
セレクト関数は、Store
の状態全体を引数として受け取り、必要な状態を返します。
// Counter.tsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { decrement, increment } from './action';
import { CounterState } from './reducer';
const Counter: React.FC = () => {
const count = useSelector<CounterState, number>((state) => state.count);
const dispatch = useDispatch();
return (
<>
<div>count: {count}</div>
<div>
<button onClick={() => dispatch(increment())}>increment</button>
</div>
<div>
<button onClick={() => dispatch(decrement())}>decrement</button>
</div>
</>
);
};
export default Counter;
6. Reduxストアの作成とプロバイダーの設定
最後に、Reduxストアを作成し、Reactアプリケーションに提供するためのプロバイダを指定しましょう。
// src/Providers.tsx
import { FC, PropsWithChildren } from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { Provider as ReduxProvider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { createStore } from 'redux';
import { counterReducer, initialState } from './reducer';
const store = createStore(counterReducer, initialState);
const Providers: FC<PropsWithChildren> = ({ children }) => (
<BrowserRouter>
<HelmetProvider>
<ReduxProvider store={store}>{children}</ReduxProvider>
</HelmetProvider>
</BrowserRouter>
);
export default Providers;
Redux 公式スタイルガイド
Reduxを利用するにあたり、公式からでているスタイルガイドも一読しておきましょう。
個人的に注意しておきたい点をピックアップしておきます。
Action
-
Action
名は「ドメインモデル/イベント種別」のフォーマットで定義する -
Action
はFSA
(Flux Standard Action)に準拠させる -
Action
は直に書かずAction Creator
を使って生成する
Design Pattern
- ロジックを書くときは
Redux Toolkit
を使う - イミュータブルな状態の更新には
Immer
を使う - ファイル構造には
Feature Folder
またはDucks
パターンを適用する
まとめ
この記事では、Reduxの基本についてまとめてみました。
難しいイメージを持っていましたが、なんとかなった...
RTK
(Redux Tool Kit)を使用した方が楽に実装できますが、プレーンなRedux
実装についても理解しておくといい気がします。