リデューサとは
useStateでは状態の更新処理が複雑になる時のソリューションである。
リデューサは何を解決するか
「何を」「どのように」変更するか管理する必要がある
useState
では値を更新する際に「何を」「どのように」更新するかを記述する必要がある。
しかし状態するときは「何を」更新するかを考えるだけにしたい。
例えば簡単なTODOリストでは下記のようになる。
const [tasks, setTasks] = useState(initialTasks);
...
// どのように更新するかを記述
function handleAddTask(text) {
// setTasks()の前後に任意の処理を入れることもある。
setTasks([
...tasks,
{
id: nextId++,
text: text,
done: false,
},
]);
}
...
return (
<>
...
<!-- AddTaskでは一つのタスクの状態を管理している -->
<AddTask onAddTask = { handleAddTask }>
...
</>
)
これにタスクを加えたときに配列全体にソート処理をかけたり、他のコンポーネントへ値を渡したりといった処理が発生し複雑な処理になることもある。
「どのように」変更するかを分割した方がそのコンポーネントに適しているのであれば、ここにsetTasks()
の処理を書くべきではない。
reducer
を使うことで「どのように」変更するかの処理だけ別のコンポーネントに移譲することができる。
const [tasks, dispatch] = useReducer(taskReducer, initialTasks);
const taskReducer = (tasks, action) => {
switch (action.type) {
// 任意のキー(ここでは'added')に対して処理を書く
// このキーのことをアクションと呼ぶ
case 'added':{
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
}
]
}
// その他のパターン
case 'changed': { }
case 'deleted': { }
default:
throw Error('Unkonwn action: ' + action.type);
}
}
// useStateの場合と比べ処理が少なくなる
function handleAddTask(text) {
dispatch({
// typeは何でもいい added_taskなどでもOK 何が起こったかを説明するもの
type: 'added',
id: nextId++,
text: text
});
}
上記のように「何を(tasksを)」更新することだけ考えればよくなる。
つまり処理を適切に分割し、関心の分離を行うことができる。
参考