useReducerとは?
useReducer
は、Reactのフックの一つで、現在の状態 (state
) とアクション (action
) を受け取って新しい状態を返すリデューサー関数を使用します。これにより、状態管理のロジックを外部に持たせることができ、テストがしやすくなるという利点があります。
reducer
は、現在のstate
とaction
を受け取って新しいstate
を返す関数です。この考え方はuseReducer
でも使われており、状態管理ライブラリであるReduxでも一般的に使用されています。
基本的な構文
reducer
: 現在の状態とアクションを受け取って新しい状態を返す関数。initialArg
: 初期状態。init
: オプショナルな初期化関数。(initialArgを引数に取り初期値を生成する関数)state
とdispatch
が返される
const [state, dispatch] = useReducer(reducer, initialArg, init?)
useReducerを使用したカウンターの例
import { useReducer } from "react"; const reducer = (state, action) => { if (action.type === "incrementedAge") { return { age: state.age + 1, }; } throw Error("エラーが発生しました"); }; const Counter = () => { const [state, dispatch] = useReducer(reducer, { age: 0 }); return ( <> <button onClick={() => { dispatch({ type: "incrementedAge" }); }} > Increment age </button> <p>{`私の年齢は${state.age}歳です`}</p> </> ); }; export default Counter;
- ボタンをクリックすると
dispatch
が{ type: "incrementedAge" }
というアクションを発行します。 reducer
関数がこのアクションを受け取り、状態を更新します。- 初回の
state
には初期値の{ age: 0 }
が設定されます。
複雑なロジックになる場合は、reducer
を別ファイルに分けることで管理しやすくなります。
useStateとの違い
useState
とuseReducer
はどちらも状態管理のためのフックですが、どのような違いがあるのかを見ていきたいと思います。
useStateを使用したカウンターの例
import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); return ( <div> {count} <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> </div> ); }; export default Counter;
useReducerを使用したカウンターの例
import { useReducer } from "react"; const Counter = () => { // 初期値は0でactionにdispatchの引数がactionの値としてReducer関数に渡される const [state, dispatch] = useReducer((state, action) => state + action, 0); return ( <div> {state} <button onClick={() => dispatch(1)}>+</button> <button onClick={() => dispatch(-1)}>-</button> </div> ); }; export default Counter;
useReducer
を使うメリットは、状態管理のロジックをコンポーネントの外に抽出できることです。これによってコンポーネント内部がスッキリさせることができ、テストのしやすさも向上されることが可能になります。
チェックボックスの例
useStateを使用した場合
import { useState } from "react"; const Checkbox = () => { const [checked, setChecked] = useState(false); return ( <div> <input type="checkbox" checked={checked} onChange={() => setChecked(!checked)} /> </div> ); }; export default Checkbox;
useReducerを使用した場合
import { useReducer } from "react"; const Checkbox = () => { const [checked, toggle] = useReducer((checked) => !checked, false); return ( <div> {/* toggleが呼ばれるとuseReducerの第一引数に渡した関数が呼ばれる */} <input type="checkbox" checked={checked} onChange={toggle} /> </div> ); }; export default Checkbox;
入力フィールドの例
useStateを使用した場合
import { useState } from "react"; const TaxCalculator = () => { const [value, setValue] = useState("0円"); return ( <div> {value} <input type="number" onChange={(e) => setValue(`${Math.round(e.target.value * 1.08)}円`)} /> </div> ); }; export default TaxCalculator;
useReducerを使用した場合
import { useReducer } from "react"; const TaxCalculator = () => { const [value, setValue] = useReducer( // コンポーネント内で現在の状態は使用しないので、第一引数は不要 (_, action) => `${Math.round(action * 1.08)}円`, "0円" ); return ( <div> {value} <input type="number" onChange={(e) => setValue(e.target.value)} /> </div> ); }; export default TaxCalculator;
状態管理をuseReducerにまとめる
以下のように状態管理が多く、可読性が悪くなってしまう場合があると思います。
ここでuseReducer
を使用して状態をまとめることで、管理しているものや初期値を分かりやすくすることができます。
const App = () => { const [isOpen, setIsOpen] = useState(false) const [type, setType] = useState('large') const [phone, setPhone] = useState('') const [email, setEmail] = useState('') const [error, setError] = useSatte(null) return ( <></> ) }
import { useReducer } from "react"; const initialState = { isOpen: true, type: "", phone: "", email: "", error: "", }; const reducer = (state, action) => { switch (action.type) { case "SET_PHONE": return { ...state, phone: action.payload }; // 特定のプロパティだけ更新 case "SET_EMAIL": return { ...state, email: action.payload }; default: return state; } }; const ReducerComponent = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <> <p>電話番号: {state.phone}</p> <p>メールアドレス: {state.email}</p> <button onClick={() => dispatch({ type: "SET_PHONE", payload: "090-0000-0000" }) } > 電話番号 </button> <button onClick={() => dispatch({ type: "SET_EMAIL", payload: "sample@example.com" }) } > メールアドレス </button> </> ); }; export default ReducerComponent;
useStateとの使い分けは?
useState
はuseReducer
で実装されているので、useState
でできることはuseReducer
でも可能なようです。
使い分けについては、useState
とuseReducer
ドキュメントには個人の好みとあるとありますが、単純な状態管理にはuseState
を使い、複数の状態が依存しているよう複雑な状態ロジックなどにはuseReducer
を使うのも良いのかなと思いました。
まとめ
useReducer
を適切に使うことができればコードをよりシンプルに管理できることが分かりました。
今後はuseReducer
の型の確認と、useState
からuseReducer
を作ったりなどして理解を深めていきたいなと思いました。
最後まで読んでくださりありがとうございます! この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いいたします!!Wantedly / Green