Hallo Habr!
Mit unglaublichem Stolz und Erleichterung haben wir heute Abend ein
neues Buch über React an die Druckerei übergeben.

Bei dieser Gelegenheit bieten wir Ihnen eine leicht gekürzte Übersetzung eines Artikels von Dan Abramov an, in dem die Verwendung von Abfangjägern in der 16. Version von React beschrieben wird. Das Buch, auf das wir uns schon freuen, ist in Kapitel 5 beschrieben.
Letzte Woche haben
Sophie Alpert und ich auf der React Conf-Konferenz das Konzept der „Interceptors“ vorgestellt, gefolgt von einer ausführlichen
Diskussion des
Themas von
Ryan Florence .
Ich empfehle dringend, dass Sie sich diese Plenarvorlesung ansehen, um sich mit den verschiedenen Problemen vertraut zu machen, die wir mithilfe von Abfangjägern lösen wollen. Selbst die Stunde Ihrer Zeit schätze ich sehr, daher habe ich mich entschlossen, in diesem Artikel kurz die wichtigsten Überlegungen für Abfangjäger zu skizzieren.
Hinweis: React Interceptors sind noch experimentell. Sie müssen sich jetzt nicht mit ihnen befassen. Beachten Sie auch, dass diese Veröffentlichung meine persönlichen Ansichten enthält, die möglicherweise nicht mit der Position der React-Entwickler übereinstimmen.
Warum werden Abfangjäger benötigt?Es ist bekannt, dass die Organisation von Komponenten und ein Datenfluss nach unten dazu beitragen, eine große Benutzeroberfläche in Form kleiner, unabhängiger und wiederverwendbarer Fragmente zu organisieren.
Es ist jedoch oft nicht möglich, komplexe Komponenten über eine bestimmte Grenze hinaus aufzubrechen, da die Logik den Zustand beibehält und nicht in eine Funktion oder eine andere Komponente extrahiert werden kann . Manchmal beschweren sich diejenigen darüber, die sagen, dass React keine „Aufgabentrennung“ erreicht.
Solche Fälle sind sehr häufig und hängen beispielsweise mit Animation, Formularverarbeitung, Verbindung zu externen Datenquellen und vielen anderen Vorgängen zusammen, die wir möglicherweise mit unseren Komponenten ausführen müssen. Wenn wir versuchen, solche Probleme nur mit Komponenten zu lösen, erhalten wir normalerweise:
- Riesige Komponenten , die schwer zu überarbeiten und zu testen sind.
- Duplizierung der Logik zwischen verschiedenen Komponenten und Lebenszyklusmethoden.
- Insbesondere komplexe Muster , die Requisiten und Komponenten höherer Ordnung rendern.
Wir glauben, dass Abfangjäger am vielversprechendsten sind, um all diese Probleme zu lösen.
Interceptors helfen dabei, die Logik innerhalb der Komponente in Form wiederverwendbarer isolierter Einheiten zu organisieren :

Interceptors entsprechen der React-Philosophie (expliziter Datenfluss und Zusammensetzung) und innerhalb einer Komponente, nicht nur zwischen Komponenten . Deshalb scheint es mir, dass Interzeptoren natürlich in das React-Komponentenmodell passen.
Im Gegensatz zu Mustern wie Rendering-Eigenschaften oder Komponenten höherer Ordnung belasten Sniffer Ihren Komponentenbaum nicht mit unnötig tiefen Anhängen. Sie haben auch nicht die
Nachteile , die Verunreinigungen inhärent sind.
Auch wenn die Abfangjäger Sie auf den ersten Blick verzerren (genau wie ich zuerst!), Empfehle ich, dieser Option eine Chance zu geben und damit zu experimentieren. Ich denke es wird dir gefallen.
Schwillt React aufgrund von Abfangjägern an?Bis wir die Abfangjäger ausführlich behandelt haben, können Sie befürchten, dass das Hinzufügen von Abfangjägern in React nur eine Multiplikation von Entitäten ist. Das ist faire Kritik. Ich denke das: Obwohl Sie kurzfristig wirklich eine zusätzliche kognitive Belastung spüren (um sie zu studieren), werden Sie sich am Ende nur besser fühlen.
Wenn Interceptors in der React-Community Fuß fassen, wird die Anzahl der Entitäten, die beim Schreiben von React-Anwendungen verwaltet werden müssen, reduziert . Mit Interceptors können Sie ständig Funktionen verwenden, anstatt zwischen Funktionen, Klassen, Komponenten höherer Ordnung und Komponenten-Rendering zu wechseln.
Was die Vergrößerung der Implementierung betrifft, so erhöht sich die React-Anwendung mit Unterstützung von Interceptors nur um etwa ~ 1,5 kB (min + gzip). Obwohl dies an sich nicht zu viel ist, ist es sehr wahrscheinlich, dass sich
bei Verwendung von Interceptors Ihre Assemblygröße sogar verringert , da Interceptor-Code normalerweise besser minimiert wird als äquivalenter Code unter Verwendung von Klassen. Das folgende Beispiel ist etwas extrem, zeigt aber deutlich, warum alles so ist (
klicken Sie , um den gesamten Thread zu erweitern):
Es gibt keine revolutionären Änderungen am Abfangvorschlag . Ihr Code funktioniert auch dann einwandfrei, wenn Sie Interceptors in neuen Komponenten verwenden. Genau das empfehlen wir: Schreiben Sie nichts global um! Es wäre ratsam zu warten, bis die Verwendung von Interceptors in allen kritischen Codes festgelegt ist. Wir sind Ihnen jedoch dankbar, wenn Sie mit der Alpha-Version 16.7 experimentieren und uns Feedback zum
Vorschlag für Abfangjäger geben sowie
Fehler melden können .
Was ist das - Abfangjäger?Um zu verstehen, was Interceptors sind, müssen Sie einen Schritt zurückgehen und überlegen, was Code-Wiederverwendung ist.
Es gibt heute viele Möglichkeiten, Logik in React-Anwendungen wiederzuverwenden. Um etwas zu berechnen, können Sie einfache Funktionen schreiben und sie dann aufrufen. Sie können auch Komponenten schreiben (die selbst Funktionen oder Klassen sein können). Die Komponenten sind leistungsfähiger, aber wenn Sie mit ihnen arbeiten, müssen Sie eine Benutzeroberfläche anzeigen. Daher ist die Verwendung von Komponenten unpraktisch, um nicht visuelle Logik zu übertragen. Wir kommen also zu komplexen Mustern wie Rendering-Eigenschaften und Komponenten höherer Ordnung.
Würde React es nicht einfacher machen, wenn es nur einen allgemeinen Weg gäbe, Code darin wiederzuverwenden, und nicht so viel?Funktionen scheinen perfekt für wiederverwendbaren Code zu sein. Das Übergeben von Logik zwischen Funktionen ist am kostengünstigsten. Der lokale Status von React kann jedoch nicht in Funktionen gespeichert werden. Sie können aus einer Klassenkomponente kein Verhalten wie "Fenstergröße und Aktualisierungsstatus verfolgen" oder "Wert für einige Zeit animieren" extrahieren, ohne den Code neu zu strukturieren oder Abstraktionen wie Observables einzuführen. Beide Ansätze erschweren den Code nur, und React ist mit seiner Einfachheit nett zu uns.
Abfangjäger lösen genau dieses Problem. Dank Interceptors können Sie React-Funktionen (z. B. state) einer Funktion verwenden, indem Sie sie nur einmal aufrufen. React bietet mehrere integrierte Interceptors, die den React-Bausteinen entsprechen: Status, Lebenszyklus und Kontext.
Da Interceptors reguläre JavaScript-Funktionen sind, können Sie die in React bereitgestellten integrierten Interceptors kombinieren, um „native Interceptors“ zu erstellen . So können komplexe Probleme mit einer einzigen Codezeile gelöst und dann in Ihrer Anwendung multipliziert oder
in der React-Community freigegeben werdenAchtung: Genau genommen gehören Ihre eigenen Abfangjäger nicht zu den Funktionen von React. Die Fähigkeit, eigene Abfangjäger zu schreiben, beruht natürlich auf ihrer sehr internen Organisation.
Zeig mir den Code!Angenommen, wir möchten eine Komponente für die aktuelle Fensterbreite abonnieren (z. B. um anderen Inhalt oder einen engeren Anzeigebereich anzuzeigen).
Ähnlicher Code kann heute auf verschiedene Arten geschrieben werden. Um beispielsweise eine Klasse zu erstellen, mehrere Lebenszyklusmethoden zu erstellen oder sogar auf Rendering-Eigenschaften zurückzugreifen oder eine Komponente höherer Ordnung anzuwenden, wenn Sie nach einer Wiederverwendung suchen. Ich denke jedoch, nichts ist vergleichbar damit:
gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eaeWenn Sie diesen Code lesen, bedeutet dies, dass er genau das tut, was er sagt . Wir verwenden die Breite des Fensters in unserer Komponente und React zeichnet Ihre Komponente neu, wenn sie sich ändert. Genau dafür werden Interceptors benötigt - um Komponenten wirklich deklarativ zu machen, selbst wenn sie Zustands- und Nebenwirkungen enthalten.
Überlegen Sie, wie dieser eigene Interceptor implementiert werden könnte. Wir könnten den lokalen Reaktionsstatus verwenden, um die aktuelle Fensterbreite beizubehalten, und den Status festlegen, wenn die Größe des Fensters mithilfe eines Nebeneffekts geändert wird:
gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eaeWie oben gezeigt, dienen die in React integrierten Interceptors wie
useState
und
useEffect
als Bausteine. Wir können sie direkt aus unseren Komponenten verwenden oder unsere eigenen Abfangjäger daraus zusammensetzen, z. B.
useWindowWidth
. Die Verwendung eigener Abfangjäger scheint nicht weniger idiomatisch zu sein als die Arbeit mit der integrierten React-API.
Weitere Informationen zu den integrierten Abfangjägern finden Sie in
diesem Test .
Interceptors sind gekapselt - jedes Mal, wenn der Interceptor aufgerufen wird, empfängt er einen isolierten lokalen Status innerhalb der aktuell ausgeführten Komponente . In diesem Beispiel ist dies nicht wichtig (die Fensterbreite ist für alle Komponenten gleich!), Aber genau darin liegt die Kraft der Abfangjäger! Sie sollen nicht den Zustand, sondern die zustandserhaltende Logik trennen.
Wir möchten den nachgelagerten Datenstrom nicht unterbrechen!Jeder Abfangjäger kann einige lokale Zustände und Nebenwirkungen enthalten. Sie können Daten zwischen mehreren Interceptors übertragen, wie dies normalerweise zwischen Funktionen der Fall ist. Sie können Argumente annehmen und Werte zurückgeben, da es sich um JavaScript-Funktionen handelt.
Hier ist ein Beispiel einer React-Animationsbibliothek, in der wir mit Interceptors experimentieren:
Beachten Sie, wie beeindruckend die Animation im gezeigten Quellcode implementiert ist: Wir übergeben Werte zwischen mehreren nativen Interceptors innerhalb derselben Renderfunktion.
Codesandbox.io/s/ppxnl191zx(Dieses Beispiel wird in
diesem Handbuch ausführlicher erläutert.)
Aufgrund der Möglichkeit, Daten zwischen Interceptors zu übertragen, sind sie sehr praktisch, um Animationen zu implementieren, Daten zu abonnieren, Formulare zu verwalten und mit anderen zustandsbehafteten Abstraktionen zu arbeiten.
Im Gegensatz zu Rendering-Eigenschaften oder Komponenten höherer Ordnung erstellen Interceptors keine „falsche Hierarchie“ in Ihrem Renderbaum . Sie ähneln eher einer zweidimensionalen Liste von "Speicherzellen", die an eine Komponente angehängt sind. Keine zusätzlichen Levels.
Was ist mit Klassen?Unsere eigenen Abfangjäger sind unserer Meinung nach das interessanteste Detail im gesamten Angebot. Damit die eigenen Abfangjäger funktionsfähig sind, muss React auf Funktionsebene die Möglichkeit bieten, den Zustand und die Nebenwirkungen zu deklarieren. Genau dies ermöglicht es uns, integrierte Interceptors wie
useState
und
useEffect
. Lesen Sie mehr dazu in der
Dokumentation .
Es stellt sich heraus, dass solche eingebauten Abfangjäger nicht nur beim Erstellen eigener Abfangjäger praktisch sind. Sie reichen auch aus, um die Komponenten als Ganzes zu bestimmen, da sie uns die notwendigen Fähigkeiten bieten - zum Beispiel den Zustand. Aus diesem Grund möchten wir, dass Interceptors in Zukunft das wichtigste Mittel zur Definition von React-Komponenten sind.
Nein, wir planen nicht, den Unterricht schrittweise abzuschaffen. Wir verwenden Zehntausende von Klassenkomponenten auf Facebook und möchten sie (genau wie Sie) auf keinen Fall neu schreiben. Wenn die React-Community jedoch Interceptors verwendet, ist es unangemessen, die beiden empfohlenen Methoden zum Schreiben von Komponenten beizubehalten. Interceptors decken alle praktischen Fälle ab, in denen Klassen verwendet werden, bieten jedoch eine größere Flexibilität beim Extrahieren, Testen und Wiederverwenden von Code. Deshalb verbinden wir Interceptors mit unseren Ideen über die Zukunft von React.
Was ist, wenn Abfangjäger Magie sind?Vielleicht werden Sie
die Abfangregeln verwirren.
Obwohl es nicht üblich ist, einen Abfangjäger auf der oberen Ebene anzurufen, möchten Sie den Zustand in dem Zustand wahrscheinlich nicht selbst bestimmen, selbst wenn Sie könnten . Zum Beispiel kann ein zustandsgebundener Zustand im Klassenzimmer nicht festgestellt werden, und seit vier Jahren der Kommunikation mit React-Benutzern habe ich keine Beschwerden darüber gehört.
Ein solches Design ist entscheidend für die Einführung eigener Interceptors, ohne übermäßiges Syntaxrauschen oder Fallstricke zu verursachen. Wir verstehen, dass es aus Gewohnheit schwierig ist, aber wir glauben, dass dieser Kompromiss angesichts der sich bietenden Möglichkeiten akzeptabel ist. Wenn Sie nicht einverstanden sind, empfehle ich Ihnen, selbst zu experimentieren und zu versuchen, wie Ihnen dieser Ansatz gefällt.
Wir verwenden seit einem Monat Produktions-Hooks, um zu sehen, ob die neuen Regeln Programmierer verwirren werden. Die Praxis zeigt, dass eine Person innerhalb weniger Stunden mit Abfangjägern meistert. Ich gestehe, dass mir diese Regeln auf den ersten Blick ketzerisch erschienen, aber dieses Gefühl ging schnell vorbei. Das war der Eindruck, den ich hatte, als ich React zum ersten Mal traf. (React hat dir nicht gefallen? Und es hat mir erst zum zweiten Mal gefallen.)
Bitte beachten Sie: Auf der Ebene der Implementierung von Interceptors gibt es auch keine Magie. Laut
Jamie bekommt sie so etwas:
gist.github.com/gaearon/62866046e396f4de9b4827eae861ff19Wir führen eine explodierte Liste von Abfangjägern und fahren jedes Mal mit der nächsten Komponente in der Liste fort, wenn Sie einen Abfangjäger verwenden. Dank der Regeln der Interceptors ist ihre Reihenfolge in jeder Rendering-Engine gleich, sodass wir bei jedem Aufruf der Komponente den richtigen Status geben können.
(
In diesem Artikel von Rudy Yardley wird alles auf den Bildern wunderschön erklärt!)
Vielleicht haben Sie sich gefragt, wo React den Zustand der Abfangjäger speichert. Am selben Ort wie der Zustand der Klassen. React verfügt über eine interne Aktualisierungswarteschlange, die die ultimative Wahrheit für jeden Status enthält, unabhängig davon, wie Sie Ihre Komponenten definieren.
Interceptors sind unabhängig von Proxys und Gettern, wie sie in modernen JavaScript-Bibliotheken üblich sind. Daher kann argumentiert werden, dass Abfangjäger weniger Magie enthalten als andere gängige Ansätze zur Lösung solcher Probleme. Nicht mehr als in
array.push
und
array.pop
(bei denen auch die Reihenfolge der Aufrufe wichtig ist!)
Das Interceptor-Design ist nicht an React gebunden. Einige Tage nach der Veröffentlichung des Vorschlags zeigten uns verschiedene Personen experimentelle Implementierungen derselben Interceptor-API für Vue, Webkomponenten und sogar gewöhnliche JavaScript-Funktionen.
Wenn Sie sich fanatisch der funktionalen Programmierung widmen und sich unwohl fühlen, wenn React beginnt, sich im Rahmen einer Implementierung auf einen veränderlichen Zustand zu verlassen. Es kann Sie jedoch trösten, dass die Interceptor-Verarbeitung in ihrer reinen Form implementiert werden kann und sich auf algebraische Effekte beschränkt (sofern diese in JavaScript unterstützt wurden). Natürlich hat sich React auf systeminterner Ebene immer auf einen veränderlichen Zustand verlassen - und genau das möchten Sie vermeiden.
Unabhängig davon, welcher Standpunkt Ihnen näher steht - pragmatisch oder dogmatisch - hoffe ich, dass Ihnen mindestens eine dieser Optionen logisch erscheint. Am wichtigsten ist meiner Meinung nach, dass Abfangjäger unsere Arbeit vereinfachen und es für Benutzer bequemer wird, zu arbeiten. Das ist es, was mich Abfangjäger so bestechen.