Writer: tokuyasu 更新日:2024/05/10
こんにちは、デジナーレ福岡オフィスの徳安です。
今回はReduxについての記事です。
Reduxとは??
Reduxとは、状態管理のためのライブラリで、Fluxアーキテクチャに則って設計されている。
単一方向しかデータが流れないのが特徴。
Reduxの主な要素
・Store
ReduxでのStateはstoreと呼ばれるオブジェクト内に保持されている。
・Action、Dispatch
ActionをDispatchする。
Reduxで状態変更を行う唯一の方法。
・Reducer
Dispatch(送信)されたActionを受け取るのがReducer。
Reducerは「現在のState(currentState)」と「受け取ったAction」を受け取り、
新しいstateの結果を返却する純粋関数。
Redux Toolkit(RTK)
Reduxの利用を簡単かつ効果的に設計されているのが
Redux Toolkitであり、公式からもRedux Toolkitの利用が推奨されている。
createSlice
reducerと状態に対応するアクションクリエーターとアクションタイプを自動的に生成する関数。
Reduxアプリケーションでは、アクションやリデューサーの定義が冗長になるが、
createSliceはこの冗長性を削減し、コードの記述を簡素化することができる。
createSliceは初期状態(initialState)、
reducer関数のオブジェクト、スライスの名前(sliceName)を受け取る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<span style="font-family: 'arial black', sans-serif">// counterSlice.ts import { createSlice } from "@reduxjs/toolkit"; type CounterState = { value: number; }; const initialState = (): CounterState => ({ value: 0 }); const counterSlice = createSlice({ // sliceName name: "counter", // initialState initialState: initialState, // stateを書き換える処理内容を記載 reducers: { increment(state: CounterState) { state.value++; }, decrement(state: CounterState) { state.value--; }, }, });</span> |
*createSliceの戻り値
1 2 3 4 5 6 7 8 9 10 11 12 |
<span style="font-family: 'arial black', sans-serif">{ name: string, reducer: ReducerFunction, actions: Record<string, ActionCreator>, caseReducers: Record<string, CaseReducer>. getInitialState: () => State, reducerPath: string, selectSlice: Selector; selectors: Record<string, Selector>, getSelectors: (selectState: (rootState: RootState) => State) => Record<string, Selector> injectInto: (injectable: Injectable, config?: InjectConfig & { reducerPath?: string }) => InjectedSlice }</span> |
このようなオブジェクト形式で返却されるので、
createSliceを定義した時点で分割代入しておくと後々使いやすい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span style="font-family: 'arial black', sans-serif">// counterSlice.ts import { createSlice } from "@reduxjs/toolkit"; type CounterState = { value: number; }; const initialState = (): CounterState => ({ value: 0 }); // NOTE: 分割代入に変更 // - const counterSlice = createSlice({ const {reducer:counterReducer, action: counterActions} = createSlice({ // sliceName name: "counter", // initialState initialState: initialState, // stateを書き換える処理内容を記載 reducers: { increment(state: CounterState) { state.value++; }, decrement(state: CounterState) { state.value--; }, }, });</span> |
configureStore
configureStoreでstoreの作成を行う。
1 2 3 4 5 6 7 8 9 10 |
<span style="font-family: 'arial black', sans-serif">// store.ts import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "./counterSlice"; export const store = configureStore({ reducer: { counter: counterReducer, } })</span> |
combineReducersを使用して、複数のsliceReducerを管理することも可能
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span style="font-family: 'arial black', sans-serif">// store.ts import { combineReducers, configureStore } from "@reduxjs/toolkit" // NOTE: 複数のsliceReducerを管理可能 const rootReducer = combineReducers({ counter: counterReducer, toast: toastReducer, }) export const store = configureStore({ reducer: rootReducer })</span> |
また、typeScriptを使用している場合は、ここでstoreの型定義までしておく。
使用する際に、自動補完が行われタイプミスなどを減らすことができる。
1 2 3 4 |
<span style="font-family: 'arial black', sans-serif">// store.ts export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch</span> |
storeを参照できるようにする
AppをProviderで囲むことで、アプリケーション全体からStoreを利用することができるようになる。
1 2 3 4 5 6 7 8 9 10 11 12 |
<span style="font-family: 'arial black', sans-serif">// main.tsx import { Provider } from "react-redux"; import { store } from "./store"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> )</span> |
storeの値を参照する
1. useSelectorをインポートし、グローバル管理しているcounter stateを取得する
2. 使用したい場所で表示する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span style="font-family: 'arial black', sans-serif">// Page.tsx import { useSelector } from "react-redux" import { RootState } from "./store" const Page = () => { // 1 const counter = useSelector((state: RootState) => state.counter) return ( <> // 2 <p>{counter.value}</p> </> ) } </span> |
storeの値を更新する
1. useDispatchをインポートし、store に紐付いた dispatch を取得する
2. 使用したい部分で(下記だとボタンを押下したアクション部分)
createSliceで定義したActionをDispatchし、状態を更新する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<span style="font-family: 'arial black', sans-serif">// CounterButton.tsx import { useDispatch } from "react-redux" import { AppDispatch } from "./store" const CounterButton = () => { // 1 const dispatch = useDispatch<AppDispatch>() const handleClickIncrement = () => { // 2 dispatch( counter.increment() ) } const handleClickDecrement = () => { // 2 dispatch( counter.decrement() ) } return ( <> <button onClick={handleClickIncrement}>+</button> <button onClick={handleClickDecrement}>-</button> </> ) }</span> |
まとめ
Redux ToolkitではActionとReducerが同じオブジェクト内で定義されるため、
記述が追いやすく、可読性が上がります。
また、Redux Toolkitを使った場合、
stateのイミュータブル性を気にすることなくコーディングができる為、
コードをより簡潔にすることができます。
今回は良く使用される関数やReact-Reduxフック を紹介しました。
記述が追いやすく、可読性が上がるので、
グローバル管理する際には、積極的にRedux Toolkitを使用していきたいと思いました。
ここまで読み進めていただきありがとうございます。