← Hooked.
useReducer
公式ドキュメント ↗type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset" }
| { type: "set"; value: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return 0;
case "set":
return action.value;
}
}
const [count, dispatch] = useReducer(reducer, 0);何をしたか(action)を、そのまま記録できる
function dispatchWithLog(action: Action, label: string) {
setLog((prev) => [label, ...prev]);
dispatch(action); // reducer自体は純粋なまま、呼び出し側でログを取る
}dispatchに渡すactionは「何をしたいか」を表すただのデータ。だから呼び出す側でそのままログに残せる。reducerの中身をシンプルに保てば、ログや巻き戻しの仕組みも作りやすくなる。
実際の使いどころ: 複数の項目を持つ状態
type Action =
{ type: "add"; text: string } | { type: "toggle"; id: number } | { type: "remove"; id: number };
function reducer(state: Todo[], action: Action): Todo[] {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "toggle":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
case "remove":
return state.filter((t) => t.id !== action.id);
}
}配列やオブジェクトのようにまとまった状態を、追加・切り替え・削除など複数の操作で更新する場面は、reducerの得意分野。操作ごとに独立したcase文になるので、状態が複雑になるほどuseStateより見通しがよくなる。