遷移を一元化し、あり得ない組み合わせを排除する純粋なリデューサーで、複雑な state を構造化します。
このレッスンを Kodokon で開くコンポーネントが一緒に変化する複数の state 値 - リクエストのステータス、データ、エラーなど - を扱うようになると、useState の呼び出しを増やすたびに遷移ロジックがすべてのイベントハンドラーに散らばってしまいます。症状はご存じのとおりです。正しい順序で呼ぶべき 3 つのセッター、そして遅かれ早かれ生まれる矛盾した state、たとえば success というステータスの隣に null でないエラーが並ぶような状態です。useReducer はこの責務を逆転させます。state は 1 つのオブジェクトになり、あらゆる変更は 1 つの中心的な関数、すなわちリデューサーが解釈する アクション を経由します。
function requestReducer(state, action) {
switch (action.type) {
case "start":
return { status: "loading", data: null, error: null };
case "resolve":
return {
status: "success",
data: action.data,
error: null,
};
case "reject":
return {
status: "error",
data: null,
error: action.error,
};
default:
throw new Error("Unknown action: " + action.type);
}
}リデューサーは純粋関数です。現在の state とアクションを受け取り、副作用なしで次の state を返します。利点はすぐに現れます。遷移が 明示的かつ網羅的 になり、あり得ない state は根本から消え(各 case が一貫したオブジェクトを再構築します)、ロジックがあちこちに散らばる代わりに 1 か所で読めるようになります。有用な警告サインがあります。同じ setState 呼び出しの連なりを 2 か所にコピー&ペーストしているなら、おそらくリデューサーが足りていません。
function UserProfile() {
const [state, dispatch] = useReducer(requestReducer, {
status: "idle",
data: null,
error: null,
});
async function load(id) {
dispatch({ type: "start" });
try {
const res = await fetch("/api/users/" + id);
const data = await res.json();
dispatch({ type: "resolve", data });
} catch (error) {
dispatch({ type: "reject", error });
}
}
return <button onClick={() => load(1)}>Load</button>;
}useState と useReducer のどちらを選ぶかは好みの問題ではありません。単純で独立した値には useState を使い、次の state が前の state に依存する場合や複数のフィールドが一緒に変化する場合は useReducer に切り替えます。さらに 2 つの利点があります。第 3 引数は 遅延初期化 を提供し(useReducer(reducer, arg, init) は最初のレンダー時にのみ init を実行します)、React は dispatch がレンダー間で同じ識別子を保つことを保証します。そのため useCallback なしで子に渡せます。