Strukturiere komplexen Zustand mit einem reinen Reducer, der Übergänge zentralisiert und unmögliche Kombinationen ausschließt.
Diese Lektion in Kodokon öffnenSobald eine Komponente mehrere Zustandswerte orchestriert, die sich gemeinsam entwickeln - ein Anfragestatus, ein paar Daten, ein Fehler -, verteilt das Vervielfachen von useState-Aufrufen die Übergangslogik über jeden Event-Handler. Du kennst das Symptom: drei Setter, die in der richtigen Reihenfolge aufgerufen werden müssen, und früher oder später ein inkonsistenter Zustand, etwa ein Status success neben einem Fehler, der nicht null ist. useReducer dreht die Verantwortung um: Der Zustand wird zu einem einzigen Objekt, und jede Änderung läuft über eine Action, die von einer zentralen Funktion, dem Reducer, interpretiert wird.
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);
}
}Der Reducer ist eine reine Funktion: Er erhält den aktuellen Zustand und eine Action und gibt dann den nächsten Zustand zurück, ohne Seiteneffekte. Die Vorteile zeigen sich sofort: Übergänge sind explizit und vollständig, unmögliche Zustände verschwinden an der Wurzel (jedes case baut ein konsistentes Objekt neu auf), und die Logik liest sich an einer einzigen Stelle, statt verstreut zu sein. Ein nützliches Warnsignal: Wenn du dieselbe Abfolge von setState-Aufrufen an zwei Stellen kopierst, fehlt dir wahrscheinlich ein Reducer.
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>;
}Die Wahl zwischen useState und useReducer ist keine Geschmacksfrage. Behalte useState für einfache, unabhängige Werte; wechsle zu useReducer, wenn der nächste Zustand vom vorherigen abhängt oder wenn sich mehrere Felder gemeinsam ändern. Zwei weitere Vorteile: Das dritte Argument bietet eine verzögerte Initialisierung (useReducer(reducer, arg, init) führt init nur beim ersten Rendern aus), und React garantiert, dass dispatch von einem Rendern zum nächsten dieselbe Identität behält - du kannst es ohne useCallback an Kinder weitergeben.