「Redux
を使えるようになりたいけど、なんだか難しそうで手が付けられない」
という人向けに、Redux
を使ってグローバルステートを扱う方法を倉庫に例えて説明します。
なお、React
を使用することが前提になります。
Reduxを使用したサンプル
サンプル(CodeSandBox)
各ページで単一のStateを扱っています。
上記のサンプルはTypeScript
と、ページ遷移にreact-router-dom
を使用しています。
Reduxの使い方
1. Redux(とか)をインストールする
npm i redux react-redux @types/react-redux
または、
yarn add redux react-redux @types/react-redux
- redux:JavaScriptアプリ用の予測可能な状態コンテナー(公式ドキュメントより)。とりあえずこれは必須です。
-
react-redux:
Redux
のHooksが使えるようになります。 -
@types/react-redux:
React-Redux
の型定義ファイル。TypeScript
を使わない場合はインストール不要です。
2. グローバルステートをどのようにして扱うかをざっくり把握する
コードを書くことから一旦離れますが、全体像をざっくりとでも把握しておいた方が後の工程で理解が必要となる概念や用語が分かりやすくなるので、前述のサンプルにおいてプラスボタンを押した際の流れと、流れに関わる用語をまとめた下図をご確認ください。
上図に出てくる用語の意味は私の解釈および流れの説明のために記載したものです。
なので、用語の意味が正しいかどうかはひとまず置いてください
(「Redux 流れ」や「Redux 図」などで検索すると、より信頼できる図が確認できます)
上図で出てくる用語は下記の通りです。
- Store(グローバルステートの倉庫)
- Action(行動)
- Type(Actionの内容)
- Dispatch(送信)
- Reducer(グローバルステートの処理係)
これらを踏まえて、次項からサンプルのコードのRedux
に係る部分を説明します。
3. 画面を作る
特筆することは無いので割愛します。
好きに書いたらいいです。
4. storeを設ける
- index.tsx
import { createStore } from "redux";
import { Provider } from "react-redux";
// Store(グローバルステートの倉庫)を設ける
const store = createStore(/* Reducer(グローバルステートの処理係。後で設定。 */);
const rootElement = document.getElementById("root");
render(
<Provider store={store}> // Storeと各コンポーネントを接続する
<App />
</Provider>,
rootElement
);
上のコードに唐突に現れたProvider
は流れ図には無かったものですが、コード内のコメントの通りStoreと各コンポーネントを接続するものと捉えてもらえればよいです。
これで下図のようなStoreができました。
ここまでの進捗を実際の倉庫で例えると、
- 倉庫を建設した(
createStore
) - 倉庫と倉庫の物資を必要とする場所との輸送ルートを設けた(
Provider
) - 物資や人員はまだ何も無い
5. Reducerを設定する
Storeを作ったものの、現状Reducerを設定していないのでStoreは空っぽです。
ということでReducerを用意して、createStore
の引数に設定します。
- /reducers/counterReducer.ts
const counter = (count = 0 /* グローバルステートcountの初期値 */, action: { type: string }) => {
switch (action.type) {
case "INCREMENT": // プラスボタンを押した場合
return count + 1;
case "DECREMENT": // マイナスボタンを押した場合
return count - 1;
default:
return count;
}
};
export default counter;
このコードにより、
- Storeにグローバルステート
count
(初期値0)を設定した - Stateに対する処理をActionのType別に設定した
ということになります。
Reducerは、画面からDispatch(送信)されたAction(行動)のType(内容)によって、Store(倉庫)にあるcount
Stateに処理を施します。
caseの文字列"INCREMENT"
と"DECREMENT"
については次項で説明します。
Reducerが用意できたのでStoreに設定します。
- index.tsx
import { createStore } from "redux";
import { Provider } from "react-redux";
import counter from "./reducers/counterReducer"; // 用意したReducerをインポート
const store = createStore(counter); // Reducerを引数に設定
const rootElement = document.getElementById("root");
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
これでStoreは完成です。
ここまでの進捗を実際の倉庫で例えると、
- 倉庫に物資を格納した(
count = 0
) - 業務内容を定めた人員を雇用した(
Switch
) - その人員を倉庫に配属した(
createStore(counter)
)
6. ActionのTypeを設定する
- 画面が起こすActionはどんなものがあるか
- それらActionをどのように定義するか
を設定します。
- /actions/counterAction.ts
// プラスボタンを押すAction
export const increment = () => {
return {
type: "INCREMENT" // このActionのTypeを"INCREMENT"と定義
};
};
// マイナスボタンを押すAction
export const decrement = () => {
return {
type: "DECREMENT" // このActionのTypeを"DECREMENT"と定義
};
};
この設定を実際の倉庫に係る業務で例えると、倉庫の物資を必要としている場所が、物資の供給を申請する際に使用する申請書を作成する業務にあたるかと思います。
このように捉えると上のコードは、
- 申請内容別に申請書を作成した
- そのうちの1種の申請書の名前を
"INCREMENT"
、もう1種を"DECREMENT"
と命名した
ということになります。
画面さんはcount
に+1した値が欲しい場合、"INCREMENT"
申請書をStoreに勤務しているReducerさんにメールで送信するイメージです。
ここで1つ前に遡って、ReducerのSwitch文の内容をご確認ください。
Switch文がStoreに勤務しているReducerさんの業務内容です。
- /reducers/counterReducer.ts
switch (action.type) { // 「申請書の内容をチェックするよ」
case "INCREMENT": // 「画面さんから送ってもらった申請書は"INCREMENT"だ」
return count + 1; // 「じゃあcountに+1してcountを画面さんに渡さなきゃ」
case "DECREMENT":
return count - 1;
default:
return count;
}
画面さんが"INCREMENT"
申請書を送信した例に倣うと、上記コードのコメントの通り、Reducerさんは送信された申請書の内容を確認し、内容に沿った処理を行ないます。
以上でActionと、ActionのTypeを定義できました。
あとはこのActionを画面が持てば、グローバルステートを扱えます。
先の例えで言うならば画面さんが申請書を持つことで、画面さんと倉庫との物流システムが完成します(倉庫に申請書があってもしょうがないですよね)
ここまでの進捗を実際の倉庫で例えると、
- 倉庫の物資を必要とする場所が、物資の供給を倉庫に申請する申請書を作成した(
counterAction.ts
)
7. ActionをDispatchする
これまでの工程で仕組みを用意できたので、あとは画面からグローバルステートを呼び出したりsetStateするコードを記述することで、「Reduxを使ってグローバルステートを扱う」ことができるようになります。
下記はグローバルステートを扱いたいコンポーネントのコード例です。
- App.tsx
import React from "react";
const Home = () => {
return (
<>
<h1>Home</h1>
<p>count: </p>
<button onClick={() => {}}>+</button>
<button onClick={() => {}}>-</button>
</>
);
};
このコードで下図のような画面ができると思います(できなかったらごめんなさい)
count:
のコロンの後はcount
の値を表示したいですが、何も表示されていません。
なのでStoreからグローバルステートであるcount
を取得し、ここにcount
の値を表示しましょう。
- App.tsx
import React from "react";
import { useSelector } from "react-redux"; // useSelectorをインポート
const Home = () => {
const count = useSelector((count: number) => count); // countをStoreから取得
return (
<>
<h1>Home</h1>
<p>count: {count}</p> {/* countを表示 */}
<button onClick={() => {}}>+</button>
<button onClick={() => {}}>-</button>
</>
);
};
突然登場したuseSelector
は実際に機能している状態を見れば分かる通り、StoreからStateを取得するHooksです。
これでcount:
のコロンの後に、グローバルステートであるcount
の値が表示されます。
最後に、ボタンを押すことでグローバルステートの値を増減させ、その値を画面に表示しましょう。
現状はプラスボタンを押してもマイナスボタンを押しても、count: 0
の値は変わりません。
下記コードのコメントが入っているコードを追加することで、グローバルステートに対してsetStateできます。
- App.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux"; // useDispatchをインポート
import { increment, decrement } from "./actions/counterAction"; // Actionをインポート
const Home = () => {
const count = useSelector((count: number) => count);
const dispatch = useDispatch(); // dispatch関数を作成
return (
<>
<h1>Home</h1>
<p>count: {count}</p>
{/* Action"increment"をDispatch */}
<button onClick={() => dispatch(increment())}>+</button>
{/* Action"decrement"をDispatch */}
<button onClick={() => dispatch(decrement())}>-</button>
</>
);
};
これでプラスボタンを押すとcount
の値が+1され、マイナスボタンを押すとcount
の値が-1されるようになりました。
ここで改めてプラスボタンを押した際の流れ図をご確認ください。
前の流れ図に比べて、より実装に沿った図にしています。
以上でReduxを使ったグローバルステートを扱う構成が構築できました。
実際の倉庫で例えると、
- 倉庫から物資を受け取った(
useSelector
) - 倉庫に物資の供給を申請できるようになった(
useDispatch
)
という体制ができたことになるかと思います。
おわり
今までの説明はRedux
のことが全く分からない人向けの説明なので、今までの説明が理解できた場合は、Redux
の公式ドキュメントを読んでReduxに対する認識をアップデートしてください。
「前に公式ドキュメントを読んだけど全然意味が分からなかった」という人は、前に比べれば何を言っているか分かる状態になっていることと思います。
というかそうであることを願っています。
Reduxがどうしても理解できなかった場合
もっとシンプルにグローバルステートを扱えるReactNというライブラリがあります。
グローバルなステートをめっちゃシンプルに扱えるReactN - Qiita