React Status (fortgeschrittene Anleitung)
React Status (fortgeschrittene Anleitung)
Das Statusmanagement in unserer Anwendung verwendet den useState
-Hook. Eine ausgefeilte Statusverwaltung nutzt zusätzlich unter Umständen den useReducer-Hook. Das Konzept der Reduzierungen in JavaScript wird kontrovers diskutiert. Auf die Diskussion werde ich hier nicht eingehen. Unbeachtet lasse ich das Thema aber ebenfalls nicht. Die Übungen am Ende dieses Abschnitts geben dir genug Material, um dir deine eigene Meinung zu bilden.
Wir werden die Statusverwaltung der stories
vom useState
-Hook in einen neuen Hook --- den useReducer
-Hook verschieben. Führe zunächst eine Reduzierer-Funktion außerhalb der App-Komponenten ein. Eine solche empfängt immer einen state
und eine action
. Basierend auf diesen beiden Argumenten gibt ein Reduzierer einen neuen Status zurück:
# start-insert
const storiesReducer = (state, action) => {
if (action.type === 'SET_STORIES') {
return action.payload;
} else {
throw new Error();
}
};
# end-insert
action
wird oft mit einem Typ type
assoziiert. Wenn dieser Typ einer Bedingung im Reduzierer entspricht (zum Beispiel action.type === ‚SET_STORIES‘)
), dann führe eine Aktion aus. Wenn dies nicht so ist, dann gib einen Fehler aus. Mit Letzterem erinnerst du dich selbst daran, dass hier die Implementierung lückenhaft ist. Die Funktion storiesReducer
prüft den Typ type
und gibt daraufhin payload
der eingehenden Aktion zurück. Dabei wird der aktuelle Status nicht zur Berechnung des neuen verwendet --- der neue ist payload
.
Tausche in der App-Komponente useState
gegen useReducer
aus, um den Status von stories
reduziert zu verwalten. Konkret löschst du die Zeile const [stories, setStories] = React.useState([]);
und fügst dafür das nachfolgende Codebeispiel ein. Der neue Hook erhält eine Reduzierer-Funktion und einen Anfangszustand als Argumente und gibt ein Array mit zwei Elementen zurück. Das erste ist der aktuelle Status, beim zweiten handelt es sich um die Statusaktualisierungsfunktion (Dispatcher), welche wir später implementieren:
const App = () => {
...
# start-insert
const [stories, dispatchStories] = React.useReducer(
storiesReducer,
[]
);
# end-insert
...
};
Nachfolgend ersetzen wir die Funktion setStories
mit dispatchStories
. setStories
wurde bisher von useState
verwendet und hat den Status direkt zurückzugeben. useReducer
gibt nichts explizit zurück, vielmehr löst es erst eine Aktion aus. Diese beinhaltet einen Typ und die optionalen Daten payload
:
const App = () => {
...
React.useEffect(() => {
setIsLoading(true);
getAsyncStories()
.then(result => {
# start-insert
dispatchStories({
type: 'SET_STORIES',
payload: result.data.stories,
});
# end-insert
setIsLoading(false);
})
.catch(() => setIsError(true));
}, []);
...
const handleRemoveStory = item => {
const newStories = stories.filter(
story => item.objectID !== story.objectID
);
# start-insert
dispatchStories({
type: 'SET_STORIES',
payload: newStories,
});
# end-insert
};
...
};
Wenn du die Anwendung im Browser öffnest, wirst du keinen Unterschied feststellen, obwohl ein Reduzierer und der useReducer
-Hook von React jetzt den Status der stories
-Liste verwalten. Auf den ersten Blick wirkt dies umständlich. Im weiteren Verlauf sehen wir uns an, wo dies nützlich ist. Analysieren wir als Nächstes mehr als einen Zustandsübergang.
handleRemoveStory
aktualisiert die stories
-Liste ebenfalls. Es ist möglich, diese Logik in die Reduzierer-Funktion zu verschieben. Dies ist ein weiteres Beispiel für deklarative Programmierung. Anstatt den Code selbst zu schreiben und dem Reduzierer zu beschreiben, wie etwas zu erledigen ist, teilen wir ihm mit was zu erledigen ist. Alle Implementierungsdetails sind im Reduzierer gekapselt.
const App = () => {
...
const handleRemoveStory = item => {
# start-insert
dispatchStories({
type: 'REMOVE_STORY',
payload: item,
});
# end-insert
};
...
};
Jetzt deckt die Reduzierer-Funktion diesen Fall in einem neuen bedingten Zustandsübergang ab. Wenn die Bedingung zum Entfernen eines Elementes erfüllt ist, verfügt der Reduzierer über alle notwendigen Implementierungsdetails. Die Aktion enthält alle Informationen, die Kennung eines Elements, um es aus dem aktuellen Status zu entfernen und eine neue Liste gefilterter stories
als Status zurückzugeben.
const storiesReducer = (state, action) => {
if (action.type === 'SET_STORIES') {
return action.payload;
# start-insert
} else if (action.type === 'REMOVE_STORY') {
return state.filter(
story => action.payload.objectID !== story.objectID
);
} else {
# end-insert
throw new Error();
}
};
Wenn du mehr Zustandsübergänge zur Reduzierer-Funktion hinzufügst, werden if
-Anweisungen unübersichtlich. Verbessere die Lesbarkeit, indem du in deinem Code switch
-Anweisung für alle Statusübergänge verwendest:
const storiesReducer = (state, action) => {
# start-insert
switch (action.type) {
case 'SET_STORIES':
return action.payload;
case 'REMOVE_STORY':
return state.filter(
story => action.payload.objectID !== story.objectID
);
default:
throw new Error();
}
# end-insert
};
In diesem Abschnitt haben wir in das JavaScript Reduzierer-Konzept hineingeschnuppert. Wir implementierten zwei Zustandsübergängen und probierten aus, wie der aktuelle Zustand mithilfe einer Aktion in einen neuen umgewandelt wird. Als Beispiel diente die stories
-Liste. Am Ende dieses Abschnitts legen wir eine stories
-Liste für die asynchron ankommenden Daten fest und entfernen ein Element aus dieser, wobei die Logik für die Aktualisierung des Status mithilfe der Aktion an einer Stelle implementiert ist: dem useReducer
-Hook.
Sieh dir die in den Übungen verlinkten Websites an, um das Reduzierer-Konzept in JavaScript und die Verwendung von Reacts useReducer Hook vollends zu verstehen.
Übungen:
- Begutachte den Quellcode dieses Abschnitts.
- Reflektiere die Änderungen gegenüber dem letzten Abschnitt.
- Lese mehr zum Thema Reduzierer in JavaScript.
- Lese mehr zu Reduzierern and dem
useReducer
-Hook in React (0, 1).