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 DanProfil gebildet wird, und dann zumSophieProfil 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 —