概要
Reactの状態管理の1つである、Redux
について学んだことをメモします。
学習内容
Reduxとは
コンポーネントツリー内でのデータ共有機能の外部パッケージの一つ。Fluxを用いている。
useContext
での状態管理で問題がある場合のみに利用するとよい。
現場で使われていることが多く、多機能のため、数ある状態管理の中でも一押し。
Fluxパターンとは
MVCモデルの代替として用いられていた考え方。Viewから直接Actionを動かすことはせず、Dispatcherを経由して一方向に流れる。
Fluxのデザインパターン
Action -> Dispatcher -> Store -> View -> Action -> Dispatcher
- 画面(View)上で操作(Action)を行う
- ActionがDispatcherに送られる
- 過去の状態(State)とDispatcherを状態をReducerでまとめてStoreを更新する
現在の状態 (prev_state) + 挙動(action) = 新しい状態(state)
大事な3つの概念
- 必ずグローバルStateを保存するStoreは一つにする
- Stateは直接書き換えず、必ずactionを通す
- reducerは純粋な関数(前回のStateとactionから新しいStateを返す)にする
Reduxの特長
- 状態の遷移がわかりやすい
- 長期的なメンテナンスに優れている
- アプリの状態を一つの領域で管理できる(一元化)
- DevToolsにより、デバッグが可能
使用方法
現在では、素のReduxを使うのではなく、Redux Toolkitを使うことが推奨されている。
手順
- Redux Toolkitをインストールする
$ yarn add @reduxjs/toolkit react-redux
-
createSlice()
を用いて、Reduxのデータ処理を作成する(ActionやReducer、Stateといった処理を、createSlice
によりまとめて書くことができる)import { createSlice, PayloadAction } from "@reduxjs/toolkit"; // createSliceでは、引数にオブジェクトを持つ const todosSlice = createSlice({ name: "todos", // 名称 initialState, // 初期値 reducers: { // 前回のstateとactionを受け取って新しいstateを返す処理(reducer) } });
-
configureStore()
を用いて、データを保持するstoreの初期値を設定するimport { configureStore } from "@reduxjs/toolkit"; export const store = configureStore({ reducer: { // createSliceで設定したreducerを指定する }, })
- Reduxを全ページ・コンポーネントで呼び出せるように、ソースの親ディレクトリ(_app.tsx等)に
Provider
でラップする(引数にstore
を指定する)import type { AppProps } from "next/app"; import { Provider } from "react-redux"; import { store } from "src/state"; export default function MyApp({ Component, pageProps }: AppProps) { return ( <Provider store={store}> <Component {...pageProps} /> </Provider> ); }
-
useSelector
を用いて、ページやコンポーネントで欲しいデータだけを呼び出すimport { FC } from "react"; import { useSelector } from "react-redux"; export const TodoCounter: FC = () => { const todos = useSelector((state) => state.todos); return ( <h2>TODO: {todos.length}件</h2> ) }
使用例
createSlice
によりReduxのデータ処理を設定する
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Todo } from "src/types";
const initialState: Todo[] = [
{ id: 1, text: "foo", isDone: false },
{ id: 2, text: "bar", isDone: true },
];
// createSliceを使えば、actionもreducerもひとまとめに書ける
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
// reduxToolkitではmutableにコードを書ける(pushなどが使える)
addTodo: (state, action: PayloadAction<Pick<Todo, "text">>) => {
state.push({
id: state.length + 1,
text: action.payload.text,
isDone: false,
})
},
toggleTodo: (state, action: PayloadAction<Pick<Todo, "id">>) => {
state.forEach((todo) => {
if (todo.id === action.payload.id) {
todo.isDone = !todo.isDone;
}
})
}
// defaultは不要。addもtoggleでもない場合、前回のStateをそのまま返すため。
}
});
// todosSliceを他のファイルで使えるようにexportする
export const { addTodo, toggleTodo } = todosSlice.actions; // createSliceのactionsは分割代入が使える
export const todosReducer = todosSlice.reducer;
store内のデータの初期値としてtodosReducer
を設定する
import { configureStore } from "@reduxjs/toolkit";
import { todosReducer } from "./todos";
export const store = configureStore({
reducer: {
todos: todosReducer,
},
})
export type RootState = ReturnType<typeof store.getState>;
全てのコンポーネントをProvider
でラップする
import type { AppProps } from "next/app";
import { Provider } from "react-redux";
import { Layout } from "src/components/Layout";
import { store } from "src/state";
export default function MyApp({ Component, pageProps }: AppProps) {
return (
// Providerで子コンポーネント全体をラップする
<Provider store={store}>
<Layout>
<Component {...pageProps} />
</Layout>
</Provider>
);
}
コンポーネント内で欲しいデータをstoreから取得する
import { FC } from "react";
import { useSelector } from "react-redux";
import { RootState } from "src/state";
export const TodoCounter: FC = () => {
// stateからtodosを取得する
const todos = useSelector((state: RootState) => state.todos);
return (
<h2>TODO: {todos.length}件</h2>
)
}
Redux DevToolsとは
GoogleChromeの拡張機能である、Redux DevToolsを用いることで、より効率的にReduxを利用できる。
Redux DevToolsの特長
- 1つ1つのアクションを細かく見れる
- 過去の状態を確認でき、かつ容易に戻すことができる
使い方
action, state, diffなどでReduxの各操作の状態のチェックできる。
参考
IT Kingdom
GitHubリポジトリ
Redux公式ページ
Redux Three Principles
Redux Style Guide
Redux Toolkit公式ページ