Vor relativ kurzer Zeit wurde React.js Version 16.8 veröffentlicht, mit der uns Hooks zur Verfügung standen. Das Konzept der Hooks ermöglicht es Ihnen, vollwertige Funktionskomponenten mit allen Funktionen von React zu schreiben, und dies auf viele Arten bequemer als mit Klassen.
Viele haben das Auftreten von Haken mit Kritik wahrgenommen, und in diesem Artikel möchte ich über einige wichtige Vorteile sprechen, die funktionale Komponenten mit Haken uns bieten, und warum wir zu ihnen wechseln sollten.
Ich werde nicht absichtlich auf die Details der Verwendung von Haken eingehen. Dies ist nicht sehr wichtig für das Verständnis der Beispiele in diesem Artikel. Ein ziemlich allgemeines Verständnis der Arbeit von React ist ausreichend. Wenn Sie genau zu diesem Thema lesen möchten, finden Sie Informationen zu Hooks in der Dokumentation . Wenn dieses Thema interessant ist, werde ich einen Artikel ausführlicher darüber schreiben, wann, welche und wie Hooks richtig verwendet werden.
Hooks erleichtern die Wiederverwendung von Code
Stellen wir uns eine Komponente vor, die eine einfache Form wiedergibt. Etwas, das einfach ein paar Eingaben ausgibt und es uns ermöglicht, sie zu bearbeiten.
So etwas würde, wenn es stark vereinfacht wäre, wie eine Klasse aussehen:
class Form extends React.Component { state = { // fields: {}, }; render() { return ( <form> {/* */} </form> ); }; }
Stellen Sie sich nun vor, wir möchten Feldwerte automatisch speichern, wenn sie sich ändern. Ich schlage vor, Deklarationen zusätzlicher Funktionen wie shallowEqual
und debounce
.
class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { // fields: {}, // , draft: { isSaving: false, lastSaved: null, }, }; saveToDraft = (data) => { if (this.state.isSaving) { return; } this.setState({ isSaving: true, }); makeSomeAPICall().then(() => { this.setState({ isSaving: false, lastSaved: new Date(), }) }); } componentDidUpdate(prevProps, prevState) { if (!shallowEqual(prevState.fields, this.state.fields)) { this.saveToDraft(this.state.fields); } } render() { return ( <form> {/* , */} {/* */} </form> ); }; }
Gleiches Beispiel, aber mit Haken:
const Form = () => { // const [fields, setFields] = useState({}); const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return ( <form> {/* , */} {/* */} </form> ); }
Wie wir sehen, ist der Unterschied noch nicht sehr groß. Wir haben den useState
Hook geändert und das Speichern nicht in componentDidUpdate
, sondern nach dem Rendern der Komponente mit dem useEffect
Hook erstellt.
Der Unterschied, den ich hier zeigen möchte (es gibt andere, die wir unten diskutieren werden): Wir können diesen Code herausholen und an einer anderen Stelle verwenden:
// useDraft const useDraft = (fields) => { const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return [draftIsSaving, draftLastSaved]; } const Form = () => { // const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/* , */} {/* */} </form> ); }
Jetzt können wir den useDraft
Hook verwenden, den wir gerade in anderen Komponenten geschrieben haben! Dies ist natürlich ein sehr vereinfachtes Beispiel, aber die Wiederverwendung derselben Funktionalität ist eine sehr nützliche Funktion.
Mit Hooks können Sie intuitiveren Code schreiben.
Stellen Sie sich eine Komponente vor (vorerst in Form einer Klasse), die beispielsweise das aktuelle Chatfenster, eine Liste möglicher Empfänger und ein Formular zum Senden einer Nachricht anzeigt. So etwas wie das:
class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(` ${this.state.currentChat} `); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; }
Das Beispiel ist sehr bedingt, eignet sich jedoch gut zur Demonstration. Stellen Sie sich diese Benutzeraktionen vor:
- Chat öffnen 1
- Senden Sie eine Nachricht (stellen Sie sich vor, dass die Anfrage lange dauert)
- Chat öffnen 2
- Nachricht über erfolgreiches Senden erhalten:
- "Chat-Nachricht gesendet 2"
Aber die Nachricht wurde an Chat 1 gesendet? Dies geschah aufgrund der Tatsache, dass die Klassenmethode nicht mit dem Wert zum Zeitpunkt des Sendens funktionierte, sondern mit dem Wert, der zum Zeitpunkt des Abschlusses der Anforderung bereits vorhanden war. Dies wäre in einem so einfachen Fall kein Problem, aber die Korrektur eines solchen Verhaltens erfordert in erster Linie zusätzliche Sorgfalt und zusätzliche Verarbeitung, und zweitens kann es eine Fehlerquelle sein.
Bei einer Funktionskomponente ist das Verhalten anders:
const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(` ${currentChat} `); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; }
Stellen Sie sich dieselben Benutzeraktionen vor:
- Chat öffnen 1
- Senden Sie eine Nachricht (die Anfrage wird erneut ausgeführt)
- Chat öffnen 2
- Nachricht über erfolgreiches Senden erhalten:
- "Chat-Nachricht gesendet 1"
Was hat sich also geändert? Was sich geändert hat, ist, dass wir jetzt für jedes Rendering, für das currentChat
anders ist, eine neue Methode erstellen. So können wir überhaupt nicht darüber nachdenken, ob sich in Zukunft etwas ändern wird - wir arbeiten mit dem, was wir jetzt haben . Jede Renderkomponente schließt in sich alles, was sich darauf bezieht .
Haken retten uns vor dem Lebenszyklus
Dieser Punkt überschneidet sich mit dem vorherigen. React ist eine Bibliothek zur deklarativen Beschreibung einer Schnittstelle. Die Deklarierbarkeit erleichtert das Schreiben und Unterstützen von Komponenten erheblich und ermöglicht es Ihnen, weniger darüber nachzudenken, was unbedingt getan werden müsste, wenn wir React nicht verwendet hätten.
Trotzdem sind wir bei der Verwendung von Klassen mit dem Lebenszyklus von Komponenten konfrontiert. Wenn Sie nicht tiefer gehen, sieht es so aus:
- Komponentenmontage
- Komponentenaktualisierung (beim Ändern des
state
oder der props
) - Entfernen von Komponenten
Es scheint bequem, aber ich bin überzeugt, dass es nur aus Gewohnheit bequem ist. Dieser Ansatz ist nicht wie Reagieren.
Stattdessen können wir mit funktionalen Komponenten mit Hooks Komponenten schreiben, wobei wir nicht an den Lebenszyklus, sondern an die Synchronisation denken. Wir schreiben die Funktion so, dass ihr Ergebnis den Zustand der Schnittstelle in Abhängigkeit von den externen Parametern und dem internen Zustand eindeutig widerspiegelt.
Der useEffect
, den viele als direkten Ersatz für componentDidMount
, componentDidUpdate
usw. betrachten, ist eigentlich für einen anderen bestimmt. Wenn wir es verwenden, sagen wir die Reaktion: "Nachdem Sie dies gerendert haben, führen Sie bitte diese Effekte aus."
Hier ist ein gutes Beispiel dafür, wie die Komponente mit dem Klickzähler aus einem großen Artikel über useEffect funktioniert :
- Reaktion: Sagen Sie mir, was ich mit diesem Status rendern soll.
- Ihre Komponente:
- Hier ist das Renderergebnis:
<p> 0 </p>
. - Führen Sie diesen Effekt aus, wenn Sie fertig sind:
() => { document.title = ' 0 ' }
.
- Reaktion: Okay. Aktualisieren der Schnittstelle. Hey, Browser, ich aktualisiere das DOM
- Browser: Großartig, ich habe gezeichnet.
- Reaktion: Super, jetzt rufe ich den Effekt auf, den ich von der Komponente erhalten habe.
- Es beginnt
() => { document.title = ' 0 ' }
Viel aussagekräftiger, nicht wahr?
Zusammenfassung
Mit React Hooks können wir einige Probleme beseitigen und das Verständnis und die Codierung von Komponenten erleichtern. Sie müssen nur das mentale Modell ändern, das wir auf sie anwenden. Funktionskomponenten sind im Wesentlichen Schnittstellenfunktionen von Parametern. Sie beschreiben alles so, wie es zu einem bestimmten Zeitpunkt sein sollte, und helfen dabei, nicht darüber nachzudenken, wie auf Änderungen reagiert werden soll.
Ja, manchmal müssen Sie lernen, wie man sie richtig verwendet , aber auf die gleiche Weise haben wir nicht sofort gelernt, wie man Komponenten in Form von Klassen verwendet.