Wie unterscheiden sich funktionale React-Komponenten von klassenbasierten Komponenten? Die traditionelle Antwort auf diese Frage lautet seit einiger Zeit: „Durch die Verwendung von Klassen können Sie eine große Anzahl von Funktionen von Komponenten verwenden, z. B. den Status.“ Mit dem Aufkommen der
Haken spiegelt diese Antwort nicht mehr den wahren Stand der Dinge wider.
Möglicherweise haben Sie gehört, dass eine dieser Arten von Komponenten eine bessere Leistung als die andere aufweist. Aber welches? Die meisten Benchmarks, die dies testen, weisen
Mängel auf , daher würde ich aufgrund ihrer Ergebnisse mit großer Vorsicht
Schlussfolgerungen ziehen . Die Leistung hängt hauptsächlich davon ab, was im Code geschieht, und nicht davon, ob Funktionskomponenten oder klassenbasierte Komponenten ausgewählt werden, um bestimmte Funktionen zu implementieren. Unsere Studie hat gezeigt, dass der Leistungsunterschied zwischen verschiedenen Arten von Komponenten vernachlässigbar ist. Es ist jedoch zu beachten, dass sich die Optimierungsstrategien für die Arbeit mit ihnen geringfügig
unterscheiden .

Auf jeden Fall
empfehle ich
nicht , vorhandene Komponenten mit neuen Technologien neu zu schreiben, wenn es keine guten Gründe dafür gibt und wenn es Ihnen nichts ausmacht, zu denen zu gehören, die diese Technologien vor allen anderen eingesetzt haben. Hooks sind immer noch eine neue Technologie (genau wie die React-Bibliothek im Jahr 2014), und einige „Best Practices“ für ihre Anwendung wurden noch nicht in die React-Handbücher aufgenommen.
Wo sind wir endlich hingekommen? Gibt es grundlegende Unterschiede zwischen den Funktionskomponenten von React und den auf Klassen basierenden Komponenten? Natürlich gibt es solche Unterschiede. Dies sind Unterschiede im mentalen Modell der Verwendung solcher Komponenten. In diesem Artikel werde ich ihren schwerwiegendsten Unterschied betrachten. Es existiert seit dem Erscheinen von Funktionskomponenten im Jahr 2015, wird aber oft übersehen. Es besteht darin, dass Funktionskomponenten gerenderte Werte erfassen. Sprechen wir darüber, was das wirklich bedeutet.
Es ist zu beachten, dass dieses Material keinen Versuch darstellt, Komponenten unterschiedlicher Typen zu bewerten. Ich beschreibe nur den Unterschied zwischen den beiden Programmiermodellen in React. Wenn Sie mehr über den Einsatz von Funktionskomponenten im Lichte von Innovationen erfahren möchten, lesen Sie
diese Liste mit Fragen und Antworten zu Haken.
Was sind die Merkmale des Codes von Komponenten, die auf Funktionen und Klassen basieren?
Betrachten Sie diese Komponente:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Es wird eine Schaltfläche angezeigt, die durch Drücken der Funktion
setTimeout
eine Netzwerkanforderung simuliert und anschließend ein Meldungsfeld zur Bestätigung des Vorgangs anzeigt. Wenn beispielsweise '
props.user
'Dan'
in
props.user
gespeichert
props.user
, wird im Nachrichtenfenster nach drei Sekunden
'Followed Dan'
angezeigt.
Beachten Sie, dass es keine Rolle spielt, ob hier Pfeilfunktionen oder Funktionsdeklarationen verwendet werden. Eine Konstruktion der Formularfunktion
function handleClick()
funktioniert genauso.
Wie schreibe ich diese Komponente als Klasse um? Wenn Sie den gerade untersuchten Code wiederholen und ihn in den Code einer Komponente konvertieren, die auf einer Klasse basiert, erhalten Sie Folgendes:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
Es ist allgemein anerkannt, dass zwei solche Codefragmente äquivalent sind. Und Entwickler sind oft völlig frei, wandeln sich im Zuge des Code-Refactorings ineinander um, ohne über die möglichen Konsequenzen nachzudenken.
Diese Codeteile scheinen gleichwertig zu seinEs gibt jedoch einen kleinen Unterschied zwischen diesen Codefragmenten. Schauen Sie sie sich genauer an. Sehen Sie den Unterschied? Zum Beispiel habe ich sie nicht sofort gesehen.
Weiter werden wir diesen Unterschied daher für diejenigen betrachten, die die Essenz dessen verstehen wollen, was selbst geschieht, ein funktionierendes Beispiel für diesen Code.
Bevor wir fortfahren, möchte ich betonen, dass der fragliche Unterschied nichts mit React-Hooks zu tun hat. In den vorherigen Beispielen werden übrigens nicht einmal Haken verwendet. Es geht um den Unterschied zwischen Funktionen und Klassen in React. Wenn Sie vorhaben, viele Funktionskomponenten in Ihren React-Anwendungen zu verwenden, möchten Sie diesen Unterschied möglicherweise verstehen.
In der Tat werden wir den Unterschied zwischen Funktionen und Klassen am Beispiel eines Fehlers veranschaulichen, der in React-Anwendungen häufig auftritt.
Der Fehler, der in React-Anwendungen häufig auftritt.
Öffnen Sie
die Beispielseite , auf der eine Liste angezeigt wird, in der Sie Benutzerprofile auswählen können, sowie zwei Schaltflächen zum
Follow
, die von den
ProfilePageClass
ProfilePageFunction
und
ProfilePageClass
angezeigt werden. Sie funktionieren und basieren auf der Klasse, deren Code oben angezeigt wird.
Versuchen Sie für jede dieser Schaltflächen, die folgende Abfolge von Aktionen auszuführen:
- Klicken Sie auf die Schaltfläche.
- Ändern Sie das ausgewählte Profil vor Ablauf von 3 Sekunden, nachdem Sie auf die Schaltfläche geklickt haben.
- Lesen Sie den im Meldungsfeld angezeigten Text.
Nachdem Sie dies getan haben, werden Sie die folgenden Funktionen bemerken:
- Wenn Sie auf die Schaltfläche klicken, die von der Funktionskomponente mit dem ausgewählten
Dan
Profil gebildet wird, und dann zum Sophie
Profil wechseln, wird im Meldungsfeld 'Followed Dan'
angezeigt. - Wenn Sie dasselbe mit einer Schaltfläche tun, die von einer Komponente basierend auf einer Klasse gebildet wird, wird
'Followed Sophie'
angezeigt.
Klassenbasierte KomponentenfunktionenIn diesem Beispiel ist das Verhalten der Funktionskomponente korrekt. Wenn ich das Profil einer Person abonniert und dann zu einem anderen Profil gewechselt habe, sollte meine Komponente nicht daran zweifeln, wessen Profil ich abonniert habe. Offensichtlich enthält die Implementierung des fraglichen Mechanismus basierend auf der Verwendung von Klassen einen Fehler (Sie sollten übrigens definitiv ein Abonnent von
Sofia werden ).
Ursachen für Fehlfunktionen klassenbasierter Komponenten
Warum verhält sich eine klassenbasierte Komponente so? Um dies zu verstehen, werfen wir einen Blick auf die
showMessage
Methode in unserer Klasse:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); };
Diese Methode liest Daten aus
this.props.user
. Die Eigenschaften in React sind unveränderlich und ändern sich daher nicht. Dies ist jedoch wie immer eine veränderbare Einheit.
Tatsächlich liegt der Zweck,
this
in einer Klasse zu haben, in der Fähigkeit,
this
zu ändern. Die React-Bibliothek selbst führt
this
Mutationen regelmäßig durch, sodass mit den neuesten Versionen der
render
und der Komponentenlebenszyklusmethoden gearbeitet werden kann.
Wenn sich unsere Komponente während der Ausführung der Anforderung erneut rendert,
this.props
sich
this.props
. Danach liest die
showMessage
Methode den
user
aus der "zu neuen"
props
.
Auf diese Weise können Sie interessante Beobachtungen zu Benutzeroberflächen machen. Wenn wir sagen, dass die Benutzeroberfläche konzeptionell eine Funktion des aktuellen Status der Anwendung ist, sind die Ereignishandler Teil der Rendering-Ergebnisse - genau wie die sichtbaren Rendering-Ergebnisse. Unsere Event-Handler gehören zusammen mit bestimmten Eigenschaften und Zuständen zu einem bestimmten Rendering-Vorgang.
Das Planen eines Timeouts, dessen Rückruf
this.props
liest, verletzt jedoch diese Verbindung. Der showMessage-
showMessage
nicht an einen bestimmten Rendering-Vorgang gebunden, sondern verliert die richtigen Eigenschaften. Das Lesen von Daten
this
unterbricht diese Verbindung.
Wie kann das Problem mithilfe klassenbasierter Komponenten gelöst werden?
Stellen Sie sich vor, dass React keine funktionalen Komponenten enthält. Wie kann man dieses Problem dann lösen?
Wir benötigen einen Mechanismus, um die Verbindung zwischen der Rendermethode mit den richtigen Eigenschaften und dem showMessage-
showMessage
, der Daten aus den Eigenschaften liest, "wiederherzustellen". Dieser Mechanismus sollte sich irgendwo befinden, wo die Essenz von
props
mit den richtigen Daten verloren geht.
Eine Möglichkeit, dies zu tun, besteht darin,
this.props
im Voraus im Ereignishandler zu lesen und dann das Gelesene explizit an die in
setTimeout
verwendete Rückruffunktion zu
setTimeout
:
class ProfilePage extends React.Component { showMessage = (user) => { alert('Followed ' + user); }; handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
Dieser Ansatz
funktioniert . Die hier verwendeten zusätzlichen Konstruktionen führen jedoch im Laufe der Zeit zu einer Zunahme des Codevolumens und zu der Tatsache, dass die Wahrscheinlichkeit von Fehlern darin zunimmt. Was ist, wenn wir mehr als eine einzelne Immobilie benötigen? Was ist, wenn wir auch mit dem Staat zusammenarbeiten müssen? Wenn die
showMessage
Methode
showMessage
andere Methode
showMessage
und diese Methode
this.props.something
oder
this.state.something
liest,
this.props.something
das gleiche Problem erneut auf. Und um es zu lösen, müssten wir
this.props
und
this.state
als Argumente an alle von
showMessage
aufgerufenen Methoden
showMessage
.
Wenn dies zutrifft, werden alle Annehmlichkeiten zerstört, die die Verwendung von auf Klassen basierenden Komponenten bietet. Es ist schwer zu merken, dass die Arbeit mit Methoden auf diese Weise schwierig und schwer zu automatisieren ist. Daher sind sich Entwickler häufig einig, dass ihre Projekte Fehler enthalten, anstatt ähnliche Methoden zu verwenden.
Ebenso
handleClick
das Einbetten von
handleClick
in
handleClick
kein globaleres Problem. Wir müssen den Code so strukturieren, dass er in viele Methoden unterteilt werden kann, aber auch, damit wir die Eigenschaften und den Status lesen können, die der mit einem bestimmten Aufruf verbundenen Renderoperation entsprechen. Dieses Problem betrifft übrigens nicht einmal ausschließlich React. Sie können es in jeder Bibliothek abspielen, um Benutzeroberflächen zu entwickeln, die Daten in solche veränderlichen Objekte einfügen.
Vielleicht können Sie, um dieses Problem zu lösen, Methoden im Konstruktor daran binden?
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert('Followed ' + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return <button onClick={this.handleClick}>Follow</button>; } }
Dies löst unser Problem jedoch nicht. Denken Sie daran, dass wir Daten aus
this.props
zu spät lesen und nicht in der verwendeten Syntax! Dieses Problem wird jedoch behoben, wenn wir uns auf JavaScript-Schließungen verlassen.
Entwickler versuchen oft, Schließungen zu vermeiden, da es
nicht einfach ist, über Werte nachzudenken, die im Laufe der Zeit nicht mutieren können. Aber die Eigenschaften in React sind unveränderlich! (Oder zumindest wird dies dringend empfohlen). Auf diese Weise können Sie aufhören, Verschlüsse als etwas wahrzunehmen, aufgrund dessen der Programmierer, wie er sagt, „sich selbst in den Fuß schießen kann“.
Dies bedeutet, dass Sie sich immer darauf verlassen können, dass sich die Eigenschaften oder der Status eines bestimmten Rendervorgangs beim Schließen nicht ändern, wenn Sie sie sperren.
class ProfilePage extends React.Component { render() {
Wie Sie sehen können, haben wir hier die Eigenschaften während des Aufrufs der
render
„erfasst“.
Vom Renderaufruf erfasste EigenschaftenBei diesem Ansatz wird für jeden Code in der
showMessage
(einschließlich
showMessage
) garantiert, dass Eigenschaften
showMessage
werden, die während eines bestimmten Aufrufs dieser Methode erfasst wurden. Infolgedessen kann React uns nicht mehr davon abhalten, das zu tun, was wir brauchen.
In der
render
können Sie so viele Hilfsfunktionen beschreiben, wie Sie möchten, und alle können die "erfassten" Eigenschaften und den Status verwenden. So haben Verschlüsse unser Problem gelöst.
Analyse der Problemlösung mittels Verschluss
Was wir gerade erreicht haben, ermöglicht es uns
, das Problem zu
lösen , aber ein solcher Code sieht seltsam aus. Warum wird eine Klasse überhaupt benötigt, wenn Funktionen innerhalb der
render
deklariert sind und nicht als Klassenmethoden?
Tatsächlich können wir diesen Code vereinfachen, indem wir die „Shell“ in Form einer Klasse, die sie umgibt, entfernen:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Hier werden wie im vorherigen Beispiel die Eigenschaften in der Funktion erfasst, da React sie als Argument an sie übergibt. Im Gegensatz
this
props
React niemals
props
.
Dies wird etwas offensichtlicher, wenn die
props
in der Funktionsdeklaration zerstört werden:
function ProfilePage({ user }) { const showMessage = () => { alert('Followed ' + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Wenn die übergeordnete Komponente
ProfilePage
mit anderen Eigenschaften
ProfilePage
, ruft React die
ProfilePage
Funktion
ProfilePage
. Der Ereignishandler, der bereits als "gehört" zum vorherigen Aufruf dieser Funktion gehört, verwendet jedoch einen eigenen
user
und einen eigenen showMessage-
showMessage
, der diesen Wert liest. All dies bleibt unberührt.
Aus diesem Grund ändert sich in der
Originalversion unseres Beispiels nichts, wenn Sie mit einer Funktionskomponente arbeiten und ein anderes Profil auswählen, nachdem Sie auf die entsprechende Schaltfläche geklickt haben, bevor die Nachricht angezeigt wird. Wenn vor dem Klicken auf die Schaltfläche ein
Sophie
Profil ausgewählt wurde, wird im Nachrichtenfenster
'Followed Sophie'
angezeigt, unabhängig davon, was passiert.
Verwendung einer FunktionskomponenteDieses Verhalten ist korrekt (möglicherweise möchten Sie sich auch für
Sunil anmelden).
Jetzt haben wir herausgefunden, was der große Unterschied zwischen Funktionen und Klassen in React ist. Wie bereits erwähnt, handelt es sich um die Tatsache, dass Funktionskomponenten Werte erfassen. Lassen Sie uns jetzt über Haken sprechen.
Haken
Bei der Verwendung von Hooks erstreckt sich das Prinzip der "Erfassung von Werten" auf den Status. Betrachten Sie das folgende Beispiel:
function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => { alert('You said: ' + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </> ); }
→
Hier können Sie mit ihm experimentieren
Obwohl dies kein beispielhaftes Beispiel für eine Messaging-Anwendungsschnittstelle ist, veranschaulicht dieses Projekt dieselbe Idee: Wenn ein Benutzer eine Nachricht gesendet hat, sollte die Komponente nicht verwechselt werden, welche Nachricht gesendet wurde. Die
message
dieser Funktionskomponente erfasst den Status, der zu der Komponente "gehört", die den Browser zum Klick-Handler für die aufgerufene Schaltfläche macht. Infolgedessen speichert die
message
, was sich zum Zeitpunkt des Klickens auf die Schaltfläche
Send
im Eingabefeld befand.
Das Problem der Erfassung von Eigenschaften und Zuständen durch Funktionskomponenten
Wir wissen, dass Funktionskomponenten in React standardmäßig Eigenschaften und Status erfassen. Was aber, wenn wir die neuesten Daten aus Eigenschaften oder Zuständen lesen müssen, die nicht zu einem bestimmten Funktionsaufruf gehören? Was ist, wenn wir sie „
aus der Zukunft lesen “ wollen?
In klassenbasierten Komponenten kann dies einfach durch Verweisen auf
this.props
oder
this.state
, da
this
eine veränderbare Entität ist. Ihre Veränderung beschäftigt sich mit Reagieren. Funktionskomponenten können auch mit veränderlichen Werten arbeiten, die von allen Komponenten gemeinsam genutzt werden. Diese Werte heißen
ref
:
function MyComponent() { const ref = useRef(null);
Der Programmierer muss diese Werte jedoch unabhängig verwalten.
Das Wesen von
ref
spielt die gleiche
Rolle wie die Felder einer Instanz einer Klasse. Dies ist ein „Notausgang“ in eine veränderliche imperative Welt. Sie kennen vielleicht das Konzept der DOM-Refs, aber diese Idee ist viel allgemeiner. Es kann mit einer Box verglichen werden, in die ein Programmierer etwas stecken kann.
Sogar äußerlich sieht eine Konstruktion der Form so
this.something
wie ein Spiegelbild der Konstruktion von
something.current
this.something
aus. Sie sind eine Darstellung des gleichen Konzepts.
Standardmäßig erstellt React keine
ref
in den Funktionskomponenten für die neuesten Eigenschafts- oder Statuswerte. In vielen Fällen benötigen Sie sie nicht, und ihre automatische Erstellung wäre Zeitverschwendung. Die Arbeit mit ihnen kann jedoch bei Bedarf selbst organisiert werden:
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => { alert('You said: ' + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
Wenn wir die
message
in
showMessage
lesen, wird die
message
showMessage
, die sich zum Zeitpunkt des Klickens auf die Schaltfläche
Send
im Feld befand. Wenn Sie jedoch
latestMessage.current
lesen, können Sie den neuesten Wert
latestMessage.current
- auch wenn wir nach dem Klicken auf die Schaltfläche
Send
weiterhin Text in das Feld eingeben.
Sie können
dieses und
dieses Beispiel vergleichen, um den Unterschied unabhängig zu bewerten. Der Wert von
ref
ist ein Weg, um die Einheitlichkeit des Renderns zu „vermeiden“. In einigen Fällen kann er sehr nützlich sein.
Im Allgemeinen sollten Sie das Lesen oder Schreiben von
ref
während des Rendervorgangs vermeiden, da diese Werte veränderbar sind. Wir bemühen uns, das Rendering vorhersehbar zu machen. Wenn wir jedoch den neuesten Wert von etwas abrufen müssen, das in Eigenschaften oder im Status gespeichert ist, kann das manuelle Aktualisieren des
ref
eine mühsame Aufgabe sein. Es kann mit folgendem Effekt automatisiert werden:
function MessageThread() { const [message, setMessage] = useState('');
→
Hier ist ein Beispiel, das diesen Code verwendet
Wir weisen dem Effekt einen Wert zu. Infolgedessen ändert sich der Wert von
ref
erst, nachdem das DOM aktualisiert wurde. Dies stellt sicher, dass unsere Mutation Funktionen wie
Time Slicing und Suspense nicht stört, die auf der Kontinuität der Rendering-Vorgänge beruhen.
Die Verwendung von
ref
auf diese Weise ist nicht oft erforderlich. Das Erfassen von Eigenschaften oder Zuständen scheint normalerweise ein viel besseres Muster des Standardsystemverhaltens zu sein. Dies kann jedoch praktisch sein, wenn Sie mit
wichtigen APIs arbeiten , z. B. solchen, die Intervalle oder Abonnements verwenden. Denken Sie daran, dass Sie auf diese Weise mit jedem Wert arbeiten können - mit Eigenschaften, mit im Status gespeicherten Variablen, mit dem gesamten
props
oder sogar mit einer Funktion.
Dieses Muster kann außerdem für Optimierungszwecke nützlich sein. Zum Beispiel, wenn sich so etwas wie
useCallback
zu oft ändert. Die
bevorzugte Lösung ist häufig die
Verwendung eines Reduzierers .
Zusammenfassung
In diesem Artikel haben wir uns eines der falschen Muster für die Verwendung klassenbasierter Komponenten angesehen und darüber gesprochen, wie dieses Problem mit Verschlüssen gelöst werden kann. Möglicherweise stellen Sie jedoch fest, dass beim Versuch, Hooks durch Angabe eines Arrays von Abhängigkeiten zu optimieren, Fehler im Zusammenhang mit veralteten Abschlüssen auftreten können. Bedeutet dies, dass die Fehler selbst ein Problem sind? Ich glaube nicht.
Wie oben gezeigt, helfen uns Verschlüsse tatsächlich dabei, kleine Probleme zu beheben, die schwer zu bemerken sind. Sie erleichtern ebenfalls das Schreiben von Code, der
parallel korrekt funktioniert. Dies ist möglich, weil die korrekten Eigenschaften und der Status, mit dem diese Komponente gerendert wurde, innerhalb der Komponente „gesperrt“ sind.
In allen Fällen, die ich bisher gesehen habe, trat das Problem der „veralteten Schließungen“ aufgrund der falschen Annahme auf, dass sich „Funktionen nicht ändern“ oder dass „Eigenschaften immer gleich bleiben“. Ich hoffe, dass Sie nach dem Lesen dieses Materials davon überzeugt sind, dass dies nicht der Fall ist.
Funktionen „erfassen“ ihre Eigenschaften und ihren Zustand - und daher ist es auch wichtig zu verstehen, welche Funktionen in Frage kommen. Dies ist kein Fehler, sondern ein Merkmal von Funktionskomponenten. Funktionen sollten beispielsweise nicht aus dem "Array von Abhängigkeiten" für
useEffect
oder
useCalback
werden. (Ein geeignetes Werkzeug zur Lösung des Problems ist normalerweise entweder
useReducer
oder
useRef
. Wir haben oben darüber gesprochen und werden in Kürze Materialien vorbereiten, die der Wahl dieses oder jenes Ansatzes gewidmet sind.)
Wenn der größte Teil des Codes in unseren Anwendungen auf Funktionskomponenten basiert, müssen wir mehr über die
Codeoptimierung wissen und wissen, welche Werte
sich im Laufe der Zeit
ändern können.
: « , , , , , ».
. , React , . , « », . , React .
, , .
React —