React Hooksの1つであるuseReducerとはどんな機能なのかを学んでいきます。
useReducerの使い方
useReducer
は、reducerと初期値の2つの引数を取ります
import { useReducer } from 'react';
const [state, dispatch] = useReducer(reducer, initialState);
useReducerは現在の状態を表すstateと、アクションを発行するための関数であるdispatchを配列で返却します。
reducer
は2つの引数を受け取り、新しい状態を返す関数です。
引数の1つ目は現在の状態、2つ目はアクションオブジェクトです。
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return {count: state.count + 1};
case 'DECREMENT':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'DECREMENT'})}>-</button>
<button onClick={() => dispatch({type: 'INCREMENT'})}>+</button>
</>
);
}
上記の例では、INCREMENTとDECREMENTの2つのアクションタイプを持つ単純なカウンターアプリケーションです。
dispatchとreducerの役割
dispatch
はreducerにアクションオブジェクトを送る関数で、このアクションオブジェクトに基づいてreducerが新しい状態を生成します。
アクションオブジェクトは一般的にtype
プロパティを持ちますが、それ以外の追加の情報も含めることができます。
一方、reducer
は現在の状態とアクションを引数にとり、新しい状態を返す関数です。
そのため、アプリケーションの状態を更新する全てのロジックをここに記述します。
ただし、reducerは純粋関数である必要があるため、実行した結果は引数が同じであれば、必ず結果も同じになる必要があります。
そのため、非同期処理のロジックはreducerの外に記述する必要があります。
まとめ
useStateを使った状態管理は、状態の更新ロジックがコンポーネントの中に閉じ込められてしまうため、コンポーネントが複雑になるにつれて、コードの見通しが悪くなりがちです。
しかし、useReducerを使うことで、状態の更新ロジックをコンポーネントの外に分離できるため、コードの見通しやテスト、コードの再利用が容易になります。
そのため、シンプルな状態管理のロジックのみであればuseState, 複雑な状態管理のロジックではuseReducerを使用するというような使い分けをしたほうが、後々メンテナンスしやすくなりそうです。