Funktionskomponenten mit React Hooks. Warum ist es besser?

Als React.js 16.8 veröffentlicht wurde, hatten wir die Möglichkeit, React Hooks zu verwenden. Mit Hooks können wir mithilfe von Funktionen voll funktionsfähige Komponenten schreiben. Wir können alle Funktionen von React.js nutzen und dies auf bequemere Weise tun.


Viele Leute stimmen der Konzeption von Hooks nicht zu. In diesem Artikel möchte ich einige wichtige Vorteile erläutern, die React Hooks Ihnen bieten, und warum wir mit Hooks schreiben müssen.


Ich werde nicht darüber sprechen, wie man Haken benutzt. Für die Beispiele ist es nicht sehr wichtig. Wenn Sie etwas zu diesem Thema lesen möchten, können Sie die offizielle Dokumentation verwenden . Auch wenn dieses Thema für Sie interessant sein wird, werde ich mehr über Hooks schreiben.


Mit Hooks können wir unseren Code problemlos wiederverwenden


Stellen wir uns eine Komponente vor, die ein einfaches Formular rendert. Es kann etwas sein, das uns einige Eingaben zeigt und es uns ermöglicht, seine Werte zu ändern.


Mit der Klassennotation wird es so etwas geben:


class Form extends React.Component { state = { // Fields values fields: {}, }; render() { return ( <form> {/* Inputs render */} </form> ); }; } 

Stellen wir uns jetzt vor, wir möchten unsere Feldwerte automatisch in einem Backend speichern, wenn sie sich ändern. Ich schlage vor, die Definition externer Funktionen wie shallowEqual und debounce zu überspringen.


 class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { // Fields values fields: {}, // Draft saving meta 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> {/* Draft saving meta render */} {/* Inputs render */} </form> ); }; } 

Dieselbe Komponente mit Haken:


 const Form = () => { // Our state 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> {/* Draft saving meta render */} {/* Inputs render */} </form> ); } 

Wie wir sehen, gibt es hier keinen großen Unterschied. Wir haben this.state durch den useState Hook ersetzt und den Entwurf jetzt im useEffect Hook useEffect .


Der Unterschied, den ich hier zeigen möchte, ist (es gibt noch andere Unterschiede, aber ich werde mich auf diesen konzentrieren): Wir können diesen Code einfach aus unserer Komponente extrahieren und woanders verwenden:


 // useDraft hook can be used in any other component 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 = () => { // Our state const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/* Draft saving meta render */} {/* Inputs render */} </form> ); } 

Und wir können useDraft hook in anderen Komponenten verwenden! Es ist natürlich ein sehr einfaches Beispiel, aber die Wiederverwendung von Code ist ziemlich wichtig, und das Beispiel zeigt, wie einfach es mit Hooks ist.


Mit Hooks können wir Komponenten intuitiver schreiben


Stellen wir uns ein Rendering einer Klassenkomponente vor, zum Beispiel einen Chat-Bildschirm, eine Chat-Liste und ein Nachrichtenformular. So:


 class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`Message is sent to chat ${this.state.currentChat}`); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; } 

Stellen Sie sich dann vor, unser Benutzer verwendet diese Chat-Komponente:


  • Sie öffnen Chat 1
  • Sie senden eine Nachricht (stellen wir uns ein langsames Netzwerk vor)
  • Sie öffnen Chat 2
  • Sie sehen eine Warnung über ihre Nachricht:
    • "Nachricht wird an Chat 2 gesendet"

Aber sie haben eine Nachricht an den zweiten Chat gesendet. Wie ist es passiert? Dies lag daran, dass die Klassenmethode mit dem aktuellen Wert arbeitet und nicht mit dem Wert, den wir beim Starten einer Nachrichtenanforderung hatten. Bei einfachen Komponenten wie diesen ist das keine große Sache, aber es kann eine Fehlerquelle in komplexeren Systemen sein.


Andererseits wirken Funktionskomponenten anders:


 const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`Message is sent to chat ${currentChat}`); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; } 

Stellen wir uns unseren Benutzer vor:


  • Sie öffnen Chat 1
  • Sie senden eine Nachricht (stellen wir uns ein langsames Netzwerk vor)
  • Sie öffnen Chat 2
  • Sie sehen eine Warnung über ihre Nachricht:
    • "Nachricht wird an Chat 1 gesendet"

Nun, was hat sich geändert? Jetzt arbeiten wir mit einem Wert, der im Render-Moment erfasst wird. Wir erstellen jedes Mal ein neues handleSubmit wenn sich currentChat ändert. Es erlaubt uns, zukünftige Veränderungen zu vergessen und über jetzt nachzudenken.


Jede Komponente rendert alles, was sie verwendet .


Haken machen den Lebenszyklus von Komponenten weg


Dieser Grund überschneidet sich stark mit dem vorherigen. React ist eine deklarative UI-Bibliothek. Durch die Deklarativität wird das Erstellen und Verarbeiten von Benutzeroberflächen einfacher. Dadurch können wir wichtige DOM-Änderungen vergessen.


Wenn wir Klassen verwenden, sehen wir uns jedoch mit dem Lebenszyklus von Komponenten konfrontiert. Es sieht so aus:


  • Montage
  • Aktualisierung (wenn sich state oder props ändern)
  • Aussteigen

Es scheint praktisch, aber ich habe es nur aufgrund unserer Gewohnheiten überzeugt. Es ist nicht wie Reagieren.


Stattdessen können wir mit funktionalen Komponenten den Code von Komponenten schreiben und den Lebenszyklus vergessen. Wir denken nur an Synchronisation . Wir schreiben, dass die Funktion unsere Benutzeroberfläche aus Eingabestützen und dem inneren Zustand macht.


Auf den ersten useEffect scheint der useEffect Hook ein Ersatz für componentDidMount , componentDidUpdate und andere Lebenszyklusmethoden zu sein. Aber so ist es nicht. Wenn wir useEffect verwenden, useEffect wir zu React: "Hey, mach das nach dem Rendern meiner Komponente".


Hier ist ein gutes Beispiel aus dem großen Artikel über useEffect :


  • Reaktion: Geben Sie mir die Benutzeroberfläche, wenn der Status 0 .
  • Ihre Komponente:
    • Hier ist das Renderergebnis: <p>You clicked 0 times</p> .
    • Denken Sie auch daran, diesen Effekt auszuführen, nachdem Sie fertig sind: () => { document.title = 'You clicked 0 times' } .
  • Reaktion: Sicher. Aktualisieren der Benutzeroberfläche. Hey Browser, ich füge dem DOM ein paar Sachen hinzu.
  • Browser: Cool, ich habe es auf den Bildschirm gemalt.
  • Reaktion: OK, jetzt werde ich den Effekt ausführen, den Sie mir gegeben haben.
    • Running () => { document.title = 'You clicked 0 times' } .

Es ist viel aussagekräftiger, nicht wahr?


Zum Schluss


React Hooks ermöglichen es uns, einige Probleme zu beseitigen und die Entwicklung zu vereinfachen. Wir müssen nur unser mentales Modell ändern. Die Funktionskomponente ist in der Tat eine Funktion der Benutzeroberfläche der Requisiten. Sie beschreiben, wie alles in jedem Moment sein muss und helfen uns, Veränderungen zu vergessen.


Nun, wir müssen lernen, wie man es benutzt, aber hey, haben Sie beim ersten Mal eine Klassenkomponente richtig geschrieben?

Source: https://habr.com/ru/post/de443500/


All Articles