Zustandsbehaftete vs. zustandslose Funktionskomponenten

React ist eine beliebte JavaScript-Frontend-Bibliothek zum Erstellen interaktiver Benutzeroberflächen. React hat eine vergleichsweise flache Lernkurve. Dies ist einer der Gründe, warum es in letzter Zeit die ganze Aufmerksamkeit auf sich zieht. 

Obwohl viele wichtige Konzepte behandelt werden müssen, sind Komponenten zweifellos das Herz und die Seele von React. Ein gutes Verständnis der Komponenten sollte Ihnen als React-Entwickler das Leben leicht machen. 

Voraussetzungen

Dieses Tutorial richtet sich an Anfänger, die mit React angefangen haben und einen besseren Überblick über die Komponenten benötigen. Wir beginnen mit den Grundlagen der Komponenten und gehen dann zu anspruchsvolleren Konzepten über, wie zum Beispiel Komponentenmuster und wann diese Muster verwendet werden. Es wurden verschiedene Komponentenklassifizierungen behandelt, z. B. Klassen- und Funktionskomponenten, zustandsbehaftete und zustandslose Komponenten sowie Container- und Präsentationskomponenten. 

Bevor Sie beginnen, möchte ich Sie mit dem Code-Snippet vertraut machen, den wir in diesem Tutorial verwenden werden. Es ist ein einfacher Zähler, der mit React erstellt wurde. Ich werde im Verlauf dieses Tutorials auf einige Teile dieses Beispiels zurückkommen. 

Also lasst uns anfangen. 

Was sind Komponenten??

Komponenten sind autarke, unabhängige Mikroeinheiten, die einen Teil Ihrer Benutzeroberfläche beschreiben. Die Benutzeroberfläche einer Anwendung kann in kleinere Komponenten aufgeteilt werden, wobei jede Komponente über eigenen Code, Struktur und API verfügt. 

Facebook hat zum Beispiel Tausende von Funktionen, die miteinander verbunden sind, wenn Sie ihre Webanwendung anzeigen. Hier ist eine interessante Tatsache: Facebook umfasst 30.000 Komponenten, und die Zahl wächst. Durch die Komponentenarchitektur können Sie jedes Teil isoliert betrachten. Jede Komponente kann alles in ihrem Bereich aktualisieren, ohne sich darüber Gedanken zu machen, wie sie sich auf andere Komponenten auswirkt. 

Wenn wir die Benutzeroberfläche von Facebook als Beispiel nehmen, wäre die Suchleiste ein guter Kandidat für eine Komponente. Der Newsfeed von Facebook würde eine andere Komponente (oder eine Komponente, die viele Unterkomponenten hostet) erstellen. Alle mit der Suchleiste befassten Methoden und AJAX-Aufrufe würden sich in dieser Komponente befinden.

Komponenten sind auch wiederverwendbar. Wenn Sie dieselbe Komponente an mehreren Stellen benötigen, ist das einfach. Mit Hilfe der JSX-Syntax können Sie Ihre Komponenten dort deklarieren, wo sie angezeigt werden sollen. 

 
Aktuelle Anzahl: this.state.count
/ * Wiederverwendbarkeit von Komponenten in Aktion. * /

Requisiten und Zustand

Komponenten benötigen Daten, um damit arbeiten zu können. Es gibt zwei Möglichkeiten, Komponenten und Daten zu kombinieren: entweder als Requisiten oder Zustand. Requisiten und Status bestimmen, was eine Komponente darstellt und wie sie sich verhält. Beginnen wir mit Requisiten.

Requisiten

Wenn Komponenten reine JavaScript-Funktionen wären, wären Requisiten die Funktionseingabe. Nach dieser Analogie akzeptiert eine Komponente eine Eingabe (die wir Requisiten nennen), verarbeitet sie und gibt dann einen JSX-Code aus.

Obwohl die Daten in Requisiten für eine Komponente verfügbar sind, lautet die Philosophie von React, dass Requisiten unveränderlich und von oben nach unten sein sollten. Dies bedeutet, dass eine übergeordnete Komponente beliebige Daten als Requisiten an ihre Kinder weitergeben kann, die untergeordnete Komponente jedoch keine Requisiten ändern kann. Wenn Sie also versuchen, die Requisiten wie unten beschrieben zu bearbeiten, erhalten Sie den TypeError "Kann schreibgeschützt nicht zuweisen".

const Button = (Requisiten) => // Requisiten können nur gelesen werden props.count = 21;…

Zustand

State hingegen ist ein Objekt, das der Komponente gehört, in der es deklariert ist. Ihr Geltungsbereich ist auf die aktuelle Komponente beschränkt. Eine Komponente kann ihren Status initialisieren und bei Bedarf aktualisieren. Der Zustand der übergeordneten Komponente endet normalerweise als Requisiten der untergeordneten Komponente. Wenn der Staat den aktuellen Geltungsbereich verlässt, bezeichnen wir ihn als Requisite.


Nachdem wir nun die Grundlagen der Komponenten kennen, werfen wir einen Blick auf die grundlegende Klassifizierung der Komponenten.

Klassenkomponenten vs. Funktionskomponenten

Eine React-Komponente kann zwei Arten haben: entweder eine Klassenkomponente oder eine Funktionskomponente. Der Unterschied zwischen den beiden ist aus ihren Namen ersichtlich. 

Funktionskomponenten

Funktionskomponenten sind lediglich JavaScript-Funktionen. Sie nehmen eine optionale Eingabe auf, die wir, wie ich bereits erwähnt habe, Requisiten nennen.


Einige Entwickler ziehen es vor, die neuen ES6-Pfeilfunktionen zum Definieren von Komponenten zu verwenden. Pfeilfunktionen sind kompakter und bieten eine kurze Syntax zum Schreiben von Funktionsausdrücken. Durch die Verwendung einer Pfeilfunktion können wir die Verwendung von zwei Schlüsselwörtern überspringen, Funktion und Rückkehr, und ein Paar geschweifte Klammern. Mit der neuen Syntax können Sie eine Komponente wie folgt in einer einzelnen Zeile definieren. 

const Hello = (Name) => (
Hallo, Name!
);

Klassenkomponenten

Klassenkomponenten bieten mehr Funktionen und mit mehr Funktionen wird mehr Gepäck bereitgestellt. Der Hauptgrund für die Auswahl von Klassenkomponenten gegenüber funktionalen Komponenten ist, dass sie vorhanden sein können Zustand.

Die state = count: 1 -Syntax ist Teil des öffentlichen Klassenfeld-Features. Mehr dazu weiter unten. 

Es gibt zwei Möglichkeiten, eine Klassenkomponente zu erstellen. Der traditionelle Weg ist zu verwenden React.createClass (). ES6 hat einen Syntaxzucker eingeführt, mit dem Sie Klassen schreiben können, die sich erweitern React.Component. Beide Methoden sollen jedoch dasselbe tun. 

Klassenkomponenten können auch ohne Zustand existieren. Hier ist ein Beispiel einer Klassenkomponente, die Eingabe-Requisiten akzeptiert und JSX rendert.

Klasse Hello erweitert React.Component Konstruktor (Requisiten) Super (Requisiten);  render () return ( 
Hallo Requisiten
)

Wir definieren eine Konstruktormethode, die Requisiten als Eingabe akzeptiert. Im Konstruktor rufen wir an Super() alles weitergeben, was von der übergeordneten Klasse geerbt wird. Hier sind einige Details, die Sie möglicherweise übersehen haben.

Erstens ist der Konstruktor beim Definieren einer Komponente optional. In dem obigen Fall hat die Komponente keinen Status, und der Konstruktor scheint nichts Nützliches zu tun. this.props im Inneren verwendet machen() funktioniert unabhängig davon, ob der Konstruktor definiert ist oder nicht. Hier ist jedoch etwas aus den offiziellen Dokumenten:

Klassenkomponenten sollten immer den Basiskonstruktor mit aufrufen Requisiten.

Als bewährte Methode empfehle ich die Verwendung des Konstruktors für alle Klassenkomponenten.

Zweitens, wenn Sie einen Konstruktor verwenden, müssen Sie anrufen Super(). Dies ist nicht optional und Sie erhalten den Syntaxfehler "Fehlender Super () Anruf Konstrukteur" Andernfalls. 

Und mein letzter Punkt betrifft die Verwendung von Super() vs. super (Requisiten). super (Requisiten) sollte verwendet werden, wenn Sie anrufen möchten this.props im Konstruktor. Ansonsten verwenden Super() alleine reicht aus.

Stateful-Komponenten vs. zustandslose Komponenten

Dies ist eine weitere beliebte Methode zum Klassifizieren von Komponenten. Und die Kriterien für die Klassifizierung sind einfach: Die Komponenten, die den Status haben, und die Komponenten, die dies nicht tun. 

Stateful-Komponenten

Stateful-Komponenten sind immer Klassenkomponenten. Wie bereits erwähnt, haben stateful-Komponenten einen Status, der im Konstruktor initialisiert wird. 

// Hier ist ein Auszug aus dem Gegenbeispiel-Konstruktor (Requisiten) Super (Requisiten); this.state = count: 0; 

Wir haben ein Zustandsobjekt erstellt und mit einer Zählung von 0 initialisiert. Es wird eine alternative Syntax vorgeschlagen, um das Aufrufen von Klassenfeldern zu erleichtern. Es ist noch nicht Teil der ECMAScript-Spezifikation, aber wenn Sie einen Babel-Transpiler verwenden, sollte diese Syntax sofort funktionieren.

Klasse App erweitert Component / * // Nicht mehr benötigt constructor () super (); this.state = count: 1 * / state = count: 1; handleCount (value) this.setState ((prevState) => (count: prevState.count + value));  render () // aus Gründen der Kürze weggelassen

Sie können die Verwendung des Konstruktors mit dieser neuen Syntax vollständig vermeiden.

Wir können jetzt auf den Zustand innerhalb der Klassenmethoden zugreifen, einschließlich machen(). Wenn Sie sie im Inneren verwenden werden machen() Um den Wert der aktuellen Anzahl anzuzeigen, müssen Sie ihn wie folgt in geschweifte Klammern setzen:

render () return (Aktuelle Anzahl: this.state.count)

Das diese Das Schlüsselwort hier bezieht sich auf die Instanz der aktuellen Komponente. 

Das Initialisieren des Status reicht nicht aus. Wir müssen den Status aktualisieren können, um eine interaktive Anwendung zu erstellen. Wenn Sie dachten, das würde funktionieren, nein, wird es nicht.

// Falscher Weg handleCount (value) this.state.count = this.state.count + value; 

 React-Komponenten sind mit einer Methode namens setState ausgestattet, um den Status zu aktualisieren. setState akzeptiert ein Objekt, das den neuen Status von enthält Anzahl.

// Das funktioniert handleCount (value) this.setState (count: this.state.count + value); 

Das setState () akzeptiert ein Objekt als Eingabe, und wir erhöhen den vorherigen Wert von count um 1, was wie erwartet funktioniert. Es gibt jedoch einen Haken. Wenn mehrere setState-Aufrufe vorhanden sind, die einen vorherigen Wert des Zustands lesen und einen neuen Wert in den Status schreiben, kann es zu einer Race-Bedingung kommen. Dies bedeutet, dass das Endergebnis nicht mit den erwarteten Werten übereinstimmt.

Hier ist ein Beispiel, das es Ihnen klar machen sollte. Versuchen Sie dies im Codesandbox-Snippet oben.

// Was ist die erwartete Ausgabe? Versuchen Sie es in der Code-Sandbox. handleCount (value) this.setState (count: this.state.count + 100); this.setState (count: this.state.count + value); this.setState (count: this.state.count-100); 

Wir möchten, dass setState die Zählung um 100 erhöht, dann um 1 aktualisiert und dann die zuvor hinzugefügten 100 entfernt. Wenn setState den Zustandsübergang in der tatsächlichen Reihenfolge ausführt, wird das erwartete Verhalten ermittelt. SetState ist jedoch asynchron, und mehrere setState-Aufrufe können für eine bessere Benutzeroberfläche und Leistung zusammengestellt werden. Der obige Code ergibt also ein Verhalten, das sich von dem unterscheidet, was wir erwarten.

Anstatt ein Objekt direkt zu übergeben, können Sie daher eine Aktualisierungsfunktion mit der Signatur übergeben:

(prevState, Requisiten) => stateChange 

prevState ist ein Verweis auf den vorherigen Status und ist garantiert auf dem neuesten Stand. Requisiten beziehen sich auf die Requisiten der Komponente, und wir brauchen keine Requisiten, um den Status hier zu aktualisieren, sodass wir das ignorieren können. Daher können wir es zur Aktualisierung des Status verwenden und die Race-Bedingung vermeiden.

// Der richtige Weg handleCount (value) this.setState ((prevState) => count: prevState.count +1); 

Das setState () rendert die Komponente erneut, und Sie verfügen über eine funktionsfähige Komponente.

Zustandslose Komponenten

Sie können entweder eine Funktion oder eine Klasse verwenden, um zustandslose Komponenten zu erstellen. Wenn Sie jedoch keinen Lebenszyklushaken in Ihren Komponenten verwenden müssen, sollten Sie sich für zustandslose Funktionskomponenten entscheiden. Es gibt viele Vorteile, wenn Sie sich dafür entscheiden, zustandslose Funktionskomponenten zu verwenden. Sie sind leicht zu schreiben, zu verstehen und zu testen, und Sie können das vermeiden diese Keyword insgesamt. Ab React v16 gibt es jedoch keine Leistungsvorteile, wenn stateless Funktionskomponenten gegenüber Klassenkomponenten verwendet werden. 

Der Nachteil ist, dass Sie keine Lebenszyklus-Hooks haben können. Die Lebenszyklusmethode ShouldComponentUpdate () wird häufig verwendet, um die Leistung zu optimieren und manuell zu steuern, was erneut gerendert wird. Sie können das noch nicht mit funktionalen Komponenten verwenden. Refs werden auch nicht unterstützt.

Containerkomponenten vs. Präsentationskomponenten

Dies ist ein weiteres Muster, das beim Schreiben von Komponenten sehr nützlich ist. Der Vorteil dieses Ansatzes ist, dass die Verhaltenslogik von der Präsentationslogik getrennt ist.

Präsentationskomponenten

Präsentationskomponenten sind mit der Ansicht oder dem Aussehen der Dinge gekoppelt. Diese Komponenten nehmen Requisiten von ihrem Containergegenstück an und rendern sie. Alles, was mit der Beschreibung der Benutzeroberfläche zu tun hat, sollte hier aufgeführt werden. 

Präsentationskomponenten sind wiederverwendbar und sollten von der Verhaltensebene abgekoppelt bleiben. Eine Präsentationskomponente empfängt die Daten und Rückrufe ausschließlich über Requisiten. Wenn ein Ereignis wie das Drücken einer Schaltfläche auftritt, führt es über Requisiten einen Rückruf an die Container-Komponente aus, um eine Ereignisbehandlungsmethode aufzurufen. 

Funktionale Komponenten sollten Ihre erste Wahl für das Schreiben von Präsentationskomponenten sein, sofern kein Zustand erforderlich ist. Wenn eine Präsentationskomponente einen Status erfordert, sollte es sich um den Status der Benutzeroberfläche und nicht um tatsächliche Daten handeln. Die Präsentationskomponente interagiert nicht mit dem Redux-Store und macht keine API-Aufrufe. 

Container-Komponenten

Container-Komponenten befassen sich mit dem Verhaltensteil. Eine Containerkomponente teilt der Präsentationskomponente mit, was mit Requisiten gerendert werden soll. Es sollte keine begrenzten DOM-Markierungen und -Stile enthalten. Wenn Sie Redux verwenden, enthält eine Containerkomponente den Code, mit dem eine Aktion an ein Geschäft gesendet wird. Alternativ können Sie hier die API-Aufrufe platzieren und das Ergebnis im Status der Komponente speichern. 

Die übliche Struktur besteht darin, dass sich oben eine Containerkomponente befindet, die die Daten als Requisiten an ihre untergeordneten Präsentationskomponenten weiterleitet. Dies funktioniert bei kleineren Projekten. Wenn das Projekt jedoch größer wird und Sie über viele Zwischenkomponenten verfügen, die lediglich Requisiten akzeptieren und an untergeordnete Komponenten weitergeben, wird dies unangenehm und schwer zu warten. In diesem Fall ist es besser, eine Containerkomponente zu erstellen, die für die Blattkomponente eindeutig ist. Dadurch werden die Zwischenkomponenten entlastet.

Was ist eine PureComponent??

Sie werden den Begriff "reine Komponente" in React-Kreisen sehr oft zu hören bekommen React.PureComponent. Wenn Sie React noch nicht kennen, kann das alles etwas verwirrend klingen. Eine Komponente gilt als rein, wenn sie bei gleichen Requisiten und Zustand garantiert das gleiche Ergebnis liefert. Eine Funktionskomponente ist ein gutes Beispiel für eine reine Komponente, da Sie bei einer Eingabe wissen, was gerendert wird. 

const HelloWorld = (Name) => ( 
'Hallo $ Name'
);

Klassenkomponenten können auch rein sein, solange ihre Requisiten und ihr Zustand unveränderlich sind. Wenn Sie eine Komponente mit einem "tiefen" unveränderlichen Satz von Requisiten und Status haben, hat die React-API etwas aufgerufen PureComponent. React.PureComponent ist ähnlich wie React.Component, aber es implementiert das ShouldComponentUpdate () Methode etwas anders. ShouldComponentUpdate () wird aufgerufen, bevor etwas erneut gerendert wird. Das Standardverhalten ist, dass true zurückgegeben wird, sodass bei jeder Änderung des Status oder der Requisiten die Komponente erneut ausgegeben wird.

shouldComponentUpdate (nextProps, nextState) return true; 

Mit PureComponent führt es jedoch einen flachen Vergleich von Objekten durch. Flacher Vergleich bedeutet, dass Sie den unmittelbaren Inhalt der Objekte vergleichen, anstatt rekursiv alle Schlüssel / Wert-Paare des Objekts zu vergleichen. Es werden also nur die Objektverweise verglichen, und wenn der Status / die Requisiten verändert sind, funktioniert dies möglicherweise nicht wie beabsichtigt. 

React.PureComponent wird zur Optimierung der Leistung verwendet, und es gibt keinen Grund, warum Sie sie verwenden sollten, es sei denn, Sie haben ein Leistungsproblem. 

Abschließende Gedanken

Zustandslose Funktionskomponenten sind eleganter und normalerweise eine gute Wahl für den Aufbau der Präsentationskomponenten. Da es sich nur um Funktionen handelt, fällt es Ihnen nicht schwer, sie zu schreiben und zu verstehen, und außerdem sind sie einfach zu testen. 

Es sollte beachtet werden, dass zustandslose Funktionskomponenten hinsichtlich Optimierung und Leistung nicht die Oberhand haben, da sie keine haben ShouldComponentUpdate () Haken. Dies kann sich in zukünftigen React-Versionen ändern, in denen Funktionskomponenten für eine bessere Leistung optimiert werden. Wenn Sie jedoch hinsichtlich der Leistung nicht kritisch sind, sollten Sie bei den funktionalen Komponenten für die Ansicht / Präsentation und den Stateful-Klassenkomponenten für den Container bleiben.

Hoffentlich haben Sie mit diesem Tutorial einen umfassenden Überblick über die komponentenbasierte Architektur und die verschiedenen Komponentenmuster in React erhalten. Was denkst du darüber? Teilen Sie sie durch die Kommentare.

In den letzten Jahren hat React an Popularität gewonnen. Tatsächlich haben wir eine Reihe von Artikeln in Envato Market, die zum Kauf, zur Überprüfung, zur Implementierung usw. zur Verfügung stehen. Wenn Sie nach weiteren Ressourcen rund um React suchen, zögern Sie nicht, sie herauszufinden.