Reduxは状態管理のライブラリです。Reactに標準で実装されてるReactHooksのContext、useContextと同じようにグローバルな状態を管理します。
Reduxは独特な用語も多いし、考え方が複雑なので参考書を読んでもイマイチ理解できません、、。
まずはインストール方法と使い方について調べました。
Reduxを使ったアプリ作成については、こちらの記事にまとめました。
プロジェクトにReact-reduxとRedux Toolkitをインストールする
ReduxはReactと名前が似ていますが、別のライブラリになるので別途インストールが必要です。
公式サイトのチュートリアルを参考にインストールします。
Reactのインストールから始める場合、npx create-react-app <プロジェクト名>を実行してReact環境を構築しておきます。
# Reactのプロジェクトを作成 ※「my-redux-counter」はプロジェクト名
$ npx create-react-app my-redux-counter
Reactのプロジェクトに移動して、React-reduxとRedux Toolkitをインストールします。
React-reduxはReactでReduxを使うためのライブラリで、Redux Toolkitは、Reduxの実装を簡単にするライブラリです。公式でも使用することが推奨されているので、両方ともインストールします。
# プロジェクトに移動
$ cd my-redux-counter
# React-reduxとRedux Toolkitをインストール
$ npm install @reduxjs/toolkit react-redux
これでReduxを使う準備ができました!
Reduxの使い方と用語
Reduxは独自の関数と用語があります。これらを把握しておかないと理解が難しいです。
簡単ですが、独自関数の使い方と用語をまとめました。
stateとは
stateは、Reduxで管理するグローバルな値(状態)を指します。
stateの値をどのコンポーネントからでも更新できて、かつ取り出せるようにするのが最終目的です。
stateは次の図のように1つが更新されたら全てのコンポーネントが更新されるイメージです。
reducer関数
reducerはstateを更新するための関数で、Reactの標準機能です。
Reduxを理解するために、reducerの挙動を理解する必要があるのでメモしておきます!
reducer(state, action) { <stateを更新する処理> }
引数(state, action)には、次の値が格納されています。
- 第一引数に更新前の値(state)
- 第二引数に新しい値(action)
第二引数のactionは、主に更新の条件を判定するtypeプロパティと、更新する値がセットになったオブジェクトを渡します。
引数で渡されたaction.typeの値をswitch文で判定して処理を分けることが多いです。Redux Toolkitを使うと異なった書き方になりますが、reducerの使用例をメモしておきます。
reducerの使用例
actionには 次のようなオブジェクトが渡ってくる
例) {type: “ADD”, id: 0, content: “テキスト”}
const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return [
...state,
{
id: action.id,
content: action.content,
}
];
case "DELETE":
const deleteItemList = state.filter((item) => item.id !== action.id);
return deleteItemList;
default:
return state;
}
};
上記のコードは、typeプロパティが”ADD”だったら新しいオブジェクトを追加して、”DELETE”だったら、該当のオブジェクトを削除しています。
なお、全ての条件に合致しない場合は、元の値stateをそのまま返しています。
尚、stateがオブジェクトや配列の場合、stateの値を直接更新せず、新しい値を作ってから返却したほうが良いらしいです。
今回は、[…state]でstate(オブジェクト)をコピーし、新しい値をつくっています。
return [
...state, // state(オブジェクト)をコピー
{
id: action.id,
content: action.content,
}
];
actionオブジェクトとは
actionオブジェクトは、stateを更新する新しい値で、reducer関数の第二引数に渡ってきます。
更新する条件を判定するためのキーワードtypeプロパティと更新値がセットになったオブジェクトです。
{type: “KEYWORD”, payload: <更新値>}
ActionCreatorでactionオブジェクトを作成する
ActionCreator(アクションクリエイター)とは、アクションを作成するだけの関数です。
以下は、plusのactionオブジェクトをActionCreatorで作成している例です。
import { useDispatch } from 'react-redux';
const Test = () => {
// useDispatchを使えるようにする
const dispatch = useDispatch();
// ActionCreatorを作成
const plus = (payload) => {
return {
type: 'plus',
payload,
};
};
// ボタンをクリックしたらDispatchする
const numPlus = () => {
dispatch(plus(2));
// {type: 'plus', payload: 2}がdispachで送信される
};
return(
<button onClick={numPlus}>
reducerに送信!
</button>
)
}
ActionCreatorを作成しているのは、以下の関数です。
引数を使ってactionオブジェクトを返却しているだけです。
// ActionCreatorを作成
const plus = (payload) => {
return {
type: 'plus',
payload,
};
};
このようにactionの作成を関数にまとめることで、actionオブジェクトのコードが統一化できます。
なお、Redux Toolkitを使うと、ActionCreatorは自動的に生成されます。
useDispatch関数
useDispatchは、reducerにactionオブジェクトを送信する為の関数です。
使用するにはインポートする必要があります。
構文は次のように、useDispatch関数を実行して、戻り値を変数に格納してから使います。
※引数には、actionオブジェクトを設定します。
const dispatch = useDispatch();
dispatch({type: “KEYWORD”, payload: <更新する値>})
以下はuseDispatch関数を使ってreducerにアクションを送信する例です。
useDispatchの使用例
// useDispatchをインポート
import { useDispatch } from 'react-redux';
const Component = () => {
// useDispatchを実行して変数に格納
const dispatch = useDispatch();
return (
<>
<button className="plus" onClick={() => dispatch({type: "KEYWORD", payload: "更新する値"})}>
ボタンをクリックしたらactionオブジェクトをreducer関数に送信する
</button>
</>
);
}
useSelector関数
useSelectorは、Reduxで管理しているグローバルな値(state)を取得する関数です。
使用するにはインポートする必要があります。
以下は、useSelector()で、stateの値を取得しています。
useSelectorの使用例
import { useSelector } from 'react-redux';
const CounterResult = () => {
const count = useSelector((state) => state);
return <div className="result">{count}</div>;
};
stateを取り出すだけなので、あまり難しいことはなさそうです。
Redux Toolkitでは、Storeに設定するプロパティに値が格納されます。
Providerコンポーネントとは
Providerコンポーネントは、Reduxで管理するstateの影響範囲を指定します。
設定するには、Providerコンポーネントで影響範囲にしたいコンポーネントを囲むだけです。
影響範囲は、子コンポーネントにも及びます。
全てのコンポーネントを対象にする場合、手っ取り早くAppコンポーネントをProviderコンポーネントで囲みます。
// Provicerコンポーネントをインポート
import { Provider } from 'react-redux';
// configureStore関数で作成したstoreをインポート
import { store } from './redux/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
propsのstoreには、これから記述するRedux ToolkitのconfigureStore関数で作成するStoreを設定します。
Redux Toolkitの使い方と用語
ここからはRedux Toolkitの機能について調べたことをメモします。
Redux ToolkitのStoreとSliceについて
Redux Toolkitの機能として、StoreとSliceがあります。
図で表すと、Storeの中にいくつかのSliceがあって、その中にreducerやactionの機能があるイメージです。最終的にはStateを更新します。
Storeとは
Storeはグローバルな値を管理するオブジェクトで、アプリに1つだけ存在します。stateの更新やアクセスは全てStoreを介して実行されます。
Sliceとは
Sliceは、stateをどのように更新するかを定義しているreducerと、stateを更新する新しい値actionの機能がセットになったオブジェクトです。
StoreとSliceを作成するには、次のように決まった関数がRedux Toolkitに用意されています。
Storeの作成 → configureStore()
Sliceの作成 → createSlice()
configureStore関数
configureStoreはStoreを作成する関数です。
引数には、stateの状態を管理する為の機能を定義する必要があります。
つまり、Storeには、Sliceを設定することになります。
以下は基本的な雛形です。ここでは、store変数にconfigureStoreを格納します。
redux/store.js
// configureStoreをインストール
import { configureStore } from '@reduxjs/toolkit';
// createSlice関数で作成したSliceをインポート
import countReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterSlice,
},
});
counterプロパティにcounterSliceという名前のSliceを設定しています。
※counterSliceは別ファイルで定義したものをインポートしています。
counterプロパティはuseSelectorで値と取り出すときに使います。
configureStoreにSliceを設定する
configureStoreの引数にはreducerプロパティを持ったオブジェクトを設定します。
このreducerプロパティに、Sliceを設定します。
言葉にするとややこしいですが、要は次のようなコードです。
configureStore({
reducer: {
reducer1: reducerSlice1,
reducer2: reducerSlice2,
},
})
複数のSliceを設定するときは、上記のようにプロパティを増やしていけばOKです。
reducerオブジェクトのプロパティには、次の項目に記載するcreateSliceで作成したreducerを設定します。
createSlice関数
createSliceはreducerとActionCreatorの機能を持ったオブジェクトを生成することができます。
createSliceで作成したオブジェクトをSliceといいます。
基本的な構文は次の通りです。
// createSliceをインポート
import { createSlice } from '@reduxjs/toolkit';
// createSliceを変数counterSliceに格納
const counterSlice = createSlice({
name: 'counter', //スライス名
initialState: 0, // 初期値
// reducerを生成
reducers: {
// タイプ名で関数を作成
plus(state, action) {
state + action.payload;
},
minus(state, { type, action }) {
state - action.payload;
},
},
});
// actionクリエイター
const { plus, minus } = counterSlice.actions;
// selectorをエクスポート
export const selectorCounter = (state) => state.counter;
// actionクリエイターとreducerをエクスポートする
export { plus, minus };
export default counterSlice.reducer;
スライス名とstateの初期値を設定するのがポイントです。※スライス名はタイプ名に使われます。
Reducerを生成する
Reducerの生成は、reducersプロパティに設定します。タイプ名で関数を作成し、実装したい処理を記述します。なお、引数には通常のreducer関数と同様、stateとactionが渡ってきます。
// reducerを生成
reducers: {
// type名で関数を作成
plus(state, action) {
state + action.payload;
},
minus(state, { type, action }) {
state - action.payload;
},
},
本来、reducerは、副作用のない純粋関数の必要がありますが、Redux Toolkitを使った場合、オブジェクトや配列でもstateを直接変更することができます。returnも必要ありません。
ActionCreatorを生成する
ActionCreatorはreducersを設定したら自動的に生成されます。
使用する際は、createSliceを格納したオブジェクトのactionsプロパティから取得できます。
※以下は分割代入で取得している例です。
// ActionCreator
const { plus, minus } = counterSlice.actions;
なお、作成されたActionCreatorのタイプ名は、[スライス名/タイプ名]になります。
例){type: “counter/plus”, payload: 0 }
ReducerとActionCreatorをエクスポートする
ReducerとActionCreatorもエクスポートして、他のコンポーネントで使えるようにしておきます。
// actionクリエイターとreducerをエクスポートする
export { plus, minus };
export default counterSlice.reducer;
Redux ToolkitでのuseSelectorの使い方
stateを取得する時、useSelector()を使いますが、React Toolkitの場合、stateはStoreのプロパティ名に含まれています。
具体的には以下のcounterの部分です。
redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import countReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterSlice,
},
});
使用する際は、次のようにuseSelector()の引数にStoreのプロパティを含めて指定します。
import { useSelector } from "react-redux";
const CounterResult = () => {
const count = useSelector((state) => state.counter);
return <div className="result">{count}</div>;
};
export default CounterResult;
これでも問題なく動きますが、stateを返す関数をエクスポートして使い回せばコードが短くなります。
// selectorをエクスポート
export const selectorCounter = (state) => state.counter;
stateを使用したいコンポーネントでエクスポートした関数を使用すれば、コードの使い回しができます。
import { useSelector } from "react-redux";
import { selectorCounter } from './counterSlice';
const CounterResult = () => {
const count = useSelector(selectorCounter);
return <div className="result">{count}</div>;
};
export default CounterResult;
まとめ
改めてReduxについてまとめましたが、やはり概念が難しいです、、。
難しいですが、決められた通りに実装すれば、簡単にグローバルな値を管理できるし、機能が分割されているので、コードの見通しが良いなぁ、、。と思いました。
これからも積極的に使っていきたいと思います!