【React】 Reduxことはじめ
React Reduxは、Reactにおけるステートを管理するためのものです。これを用いることで、コンポーネント間のデータのやり取りをスムーズに行うことができます。
目次
Reduxの利点
Reduxを用いることで、ステートの管理を容易に行うことができるようになります。今まではステートをprop
で渡しました。更新する際は親にイベントを伝えて更新を行いました。
この時、親子関係にあるコンポーネントは良いですが、そうではないコンポーネント間でのステートのやり取りは困難です。Reduxでは、そういったやり取りも容易に行なえます。

また、Reactでは、非同期な更新ができませんでした。
const updateHoge = useCallback(async () => { // ... // NG 非同期な関数のため、ステートの更新ができない setHoge(100); });
Reduxでは、非同期にステートを更新することができます。 (執筆中)
Reduxの導入
npm
でreduxを導入する場合は、react-redux @reduxjs/toolkit @types/react-redux
の3つを導入すると良いです。
npm i react-redux @reduxjs/toolkit npm i -D @types/react-redux
もしcreate-react-app
で新規で開発を行う場合は、create-react-app
にあるredux
のテンプレートを用いるのも良いです。
npm i -D create-react-app # myappは適当なアプリ名で npx create-react-app myapp --template redux
Reduxのステートの更新の流れ
Reactにおいては、ViewからActionsを起こし、それがStateを更新し、Viewに反映されるという一方向に処理が流れます。useState
を使用した際はこのように処理が流れ、シンプルな値の扱いができます。しかし、コンポーネント間で複雑なデータのやり取りが絡むと途端に管理が難しくなります。
Reduxでは、ステートを (内部では) 1箇所で管理し、どのように更新するかを定義し、それを利用してステートが更新されます。更新する内容を定義した関数は我々が直接呼び出すわけではなく、更新時にReduxが勝手に呼び出します。
ところで、Reduxの公式ドキュメントには、データの流れを示した次のような図があります。

この流れを言葉で表すと、まず初期値を定義した上で、
- ボタンなどがクリックされる
- 普通にイベントが発火して、dispatchする (このときに、更新に使う関数を指定する)
- Reduxが、指定された関数を使ってステートを更新
- ビューに反映される
という流れです。
Reduxを使う
早速Reduxを使ってみましょう。今回はcreate-react-app
のredux
テンプレートで作成されるものを参考に用意していきます。
Reduxを利用できるようにする
まずは、そのReactのアプリで、Reduxが使える範囲を指定します。アプリ全体でも問題ありませんが、パフォーマンス等の都合で分けたい場合は分けると良いです。
// ... import { Provider } from 'react-redux'; // 後で追加します import { store } from './app/store'; ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> {/* ここで指定したstoreが使用できる */} <Provider store={store}> <App /> </Provider> </React.StrictMode> );
sliceの準備
sliceを用いると、ステートの初期値、名前、Reducer、Actionの4つを一括で管理できます。Reducerはステートを更新するためのもので、Actionは更新の際に使うデータなどが入っているものです。ReducerはActionを受け取って更新します。(受け取る必要がない場合は省略できます。)
まずは箱を作って、export
しておきましょう。
import { createSlice } from '@reduxjs/toolkit'; export const counterSlice = createSlice({ // ここに定義を書く });
名前決め
まずはname
キーを使って、そのsliceの名前を決めます。
export const counterSlice = createSlice({ name: 'counter', });
ステートの初期値
ステートの初期値は、initialState
キーに入れます。連想配列内に直接書いても良いですが、可読性を上げるため、外出しする場合も多いです。
// ステートの初期状態 const initialState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, });
Reducerを定義
Reducerは、状態を更新するためのものです。ここにどのように更新するかを定義します。今回は、1増やす、1減らす、指定数増やすの3つを用意します。
export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, });
各Reducerの第1引数は、これが呼ばれた時点でのステートです。これを直接書き換えて値を更新します。
第2引数には、dispatchする際に関数に入れた値がaction.payload
内にそのまま入ってきます。必要なければ省略して問題ありません。
Reducerをexport
作ったreducerを利用できるように、export
しましょう。作ったreducerは、slice.actions
で取れます。
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
state取得用の関数をexport
stateを取得するには、Reducer本体のstateから取り出す必要があります。そのための実装を用意します。
取り出すには、Reduxが呼び出すための関数を用意し、その第1引数にRootState
型の値を受け取ります。RootState
には、Reduxに入れたすべてのstateが入っています。
この中から適切なstateを取り出すには、createSlice
で指定したname
を用いて値を取り出します。
export const selectCount = (state) => state.counter.value;
作った関数は、useSelector
で値を取得する際に使用します。
これで値を取り出すための関数ができました。
reducerをexport
次に、ReduxがどのようなReducerがあるかを認識するために、reducerをexport
しておきます。
export default counterSlice.reducer;
counterSlice全体
ここまでのコードを並べると、次のようになります。
import { createSlice } from '@reduxjs/toolkit'; const initialState = { value: 0, }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export const selectCount = (state) => state.counter.value; export default counterSlice.reducer;
storeを設定
storeは、プログラミングでは格納するという意味でしばしば使用されます。例えば、データをストレージにストアする、というような言い方をします。
ここでは、createSlice
で作成したreducer
をconfigureStore
で登録することで、Reduxにどんなreducerがあるかを伝えます。
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from '../features/counter/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, });
上の例では今回作成したcounter
のReducerだけですが、複数ある場合はすべてここに登録していきます。
さて。これでRedux側のデータの取り扱い周りの作成は完了しました。次は実際に利用するところを見ていきます。
値を取得
Reduxで管理している値を取得するには、useSelector
上でexport
したselectorを用います。
import { useSelector } from 'react-redux'; import { selectCount } from './features/counter/counterSlice'; export default function App() { // selectCount() ではない const count = useSelector(selectCount); return ( <> <p>{ count }</p> </> ); }
値を更新
値を更新する際は、useDispatch
を用いてディスパッチします。引数には、値更新で動かしたいアクションを指定します。
import { useSelector, useDispatch } from 'react-redux'; import { increment, selectCount } from './features/counter/counterSlice'; export default function App() { const count = useSelector(selectCount); // 必ず忘れないようにする!! const dispatch = useDispatch(); const handleIncrement = () => { dispatch(increment()); } return ( <> <p>{ count }</p> <button onClick={handleIncrement}>+1</button> </> ); }
dispatch(increment());
する処理をuseCallback
内にはあまり入れません。その関数を子のコンポーネントに渡す必要がない場合が多いためです。
もし子コンポーネントに関数を渡すのであればuseCallback
しますが、そもそもReduxにある値はどこからでも更新できるので、関数ごと渡す必要性が低いです。
実際に動かしてボタンを押すと、1ずつ増えていくことがわかります。
actionを指定
reducerの実装時に、
incrementByAmount: (state, action) => { state.value += action.payload; },
を作成しました。ここのaction
という引数に値を入れるには、dispatchするときにアクションの引数に伝えたい値を入れます。
import { increment, incrementByAmount, selectCount } from './features/counter/counterSlice'; export default function App() { const count = useSelector(selectCount); const dispatch = useDispatch(); const handleIncrementByAmount = () => { // 関数の第1引数に入れる dispatch(incrementByAmount(5)); } return ( <> <p>{ count }</p> <button onClick={handleIncrementByAmount}>+5</button> </> ); }
このようにして値を入れることで、reducerに値を伝えることができます。action
引数では、action.payload
とすると、入れた値を取得できます。
複数の値を入れたい場合は、連想配列を用いると良いです。
dispatch(hoge({ abc: 100, foo: "bar", }));
incrementByAmount: (state, action) => { state.value += action.payload.abc; state.baz = `foo: ${action.payload.bar}`; },
まとめ
Reduxは、Reactでステートを管理するためのシステムです。Reduxを用いることで1箇所でステート管理できるので、どのコンポーネントからも同じ値にアクセスし、更新することができます。
まずは基本的な使い方を覚えて、その後より良い設計や非同期の更新などを学んでいきましょう。
