Komponenten der endgültigen Reaktion

Was ich am React-Ökosystem mag, ist, dass IDEA hinter vielen Entscheidungen steckt. Verschiedene Autoren schreiben verschiedene Artikel zur Unterstützung der bestehenden Ordnung und erklären, warum alles „richtig“ ist, damit jeder versteht, dass die Partei auf dem richtigen Weg ist.


Nach einiger Zeit ändert sich die IDEE ein wenig und alles beginnt von vorne.


Und der Anfang dieser Geschichte ist die Trennung von Komponenten in Container und Nicht-Container (im Volksmund dumme Komponenten genannt, entschuldigen Sie mein Französisch).



Das Problem


Das Problem ist sehr einfach - Unit-Tests. In letzter Zeit gab es einige Bewegungen in Richtung Integrationstests - nun, Sie wissen: "Schreiben Sie Tests. Nicht zu viele. Meistens Integration." . Dies ist keine schlechte Idee, und wenn die Zeit knapp ist (und Tests nicht besonders benötigt werden), müssen Sie dies tun. Nennen wir es einfach Rauchtests - um sicherzustellen, dass nichts zu explodieren scheint .


Wenn viel Zeit zur Verfügung steht und Tests erforderlich sind, ist es besser, diesen Weg nicht zu gehen, da das Schreiben guter Integrationstests sehr, sehr lang ist. Nur weil sie wachsen und wachsen und um die dritte Schaltfläche auf der rechten Seite zu testen, müssen Sie zuerst auf 3 Schaltflächen im Menü klicken und nicht vergessen, sich anzumelden. Im Allgemeinen - hier ist eine kombinatorische Explosion auf einem Silbertablett.


Die Lösung hier ist ein (per Definition) einfacher Unit-Test. Die Möglichkeit, Tests mit einem vorgefertigten Status eines Teils der Anwendung zu starten. Genauer gesagt, um den Testbereich von der Anwendung oder dem großen Block auf etwas Kleines zu verkleinern - eine Einheit, egal was es ist. Es ist nicht notwendig, Enzym zu verwenden - Sie können Browsertests durchführen, wenn die Seele danach fragt. Das Wichtigste dabei ist, etwas isoliert testen zu können. Und ohne zu viel Mühe.


Isolation ist einer der wichtigsten Punkte beim Unit-Test, und deshalb mögen Unit-Tests das nicht. Sie mögen es aus verschiedenen Gründen nicht:


  • Beispielsweise wird Ihre „Einheit“ aus der Anwendung herausgerissen und funktioniert in ihrer Zusammensetzung nicht, selbst wenn die eigenen Tests grün sind.
  • oder zum Beispiel, weil die Isolation ein so kugelförmiges Pferd in einem Vakuum ist, das niemand gesehen hat. Wie erreicht man es und wie misst man es?

Persönlich sehe ich hier keine Probleme. Nach dem ersten Punkt können natürlich Integrationstests empfohlen werden, sie wurden dafür erfunden - um zu überprüfen, wie die vorgetesteten Komponenten korrekt zusammengebaut wurden. Sie vertrauen npm-Paketen, die natürlich nur sich selbst und nicht sich selbst als Teil Ihrer Anwendung testen. Wie unterscheiden sich Ihre "Komponenten" von "nicht Ihren" Paketen?


Mit dem zweiten Absatz ist alles etwas komplizierter. Und in diesem Artikel geht es genau um diesen Punkt (und alles davor - eine Einführung) - darum, wie man eine "Einheit" -Einheit testbar macht .


Teilen und erobern


Die Idee, die React-Komponenten in "Container" und "Präsentation" zu unterteilen, ist nicht neu, gut beschrieben und hat es bereits geschafft, ein wenig veraltet zu sein. Wenn wir einen Artikel von Dan Abramov als Grundlage nehmen (was 99% der Entwickler tun), dann ist die Präsentationskomponente:


  • Sind besorgt darüber, wie die Dinge aussehen
  • Kann sowohl Präsentations- als auch Containerkomponenten enthalten ** und normalerweise einige DOM-Markups und eigene Stile haben.
  • Support-Slots (Ermöglichen häufig die Eindämmung über this.props.children)
  • Anwendungsunabhängig (Keine Abhängigkeiten vom Rest der App, z. B. Flux-Aktionen oder -Speicher)
  • Nicht von Daten abhängig (Geben Sie nicht an, wie die Daten geladen oder mutiert werden).
  • Die Schnittstelle basiert auf Requisiten (Daten und Rückrufe ausschließlich über Requisiten empfangen)
  • Oft zustandslos (haben selten einen eigenen Status (wenn sie dies tun, ist es eher der UI-Status als Daten))
  • Oft SFC (werden als funktionale Komponenten geschrieben, es sei denn, sie benötigen Status-, Lebenszyklus-Hooks oder Leistungsoptimierungen)

Container sind die gesamte Logik, der gesamte Zugriff auf Daten und die gesamte Anwendung im Prinzip.


In einer idealen Welt sind Container der Kofferraum und Präsentationskomponenten die Blätter.

In Dans Definition gibt es zwei wichtige Punkte: "Anwendungsunabhängig" , was fast eine akademische Definition von "Einheit" ist, und * "Kann sowohl andere Präsentationskomponenten als auch Container enthalten ** " *, wobei diese Sterne besonders interessant sind.


(freie Übersetzung) ** In den frühen Versionen meines Artikels sagte ich (Dan), dass Präsentationskomponenten nur andere Präsentationskomponenten enthalten sollten. Das glaube ich nicht mehr. Der Komponententyp ist das Detail und kann sich im Laufe der Zeit ändern. Teilen Sie es im Allgemeinen nicht und alles wird in Ordnung sein.

Erinnern wir uns, was danach passiert:


  • Im Bilderbuch fällt alles, weil eine Art Container im dritten Knopf links in die Seite kriecht, in deren Seite sich keiner befindet. Besondere Grüße an graphql, React-Router und andere React-Intl.
  • Die Fähigkeit, Mount in den Tests zu verwenden, geht verloren, da alles von A bis Z gerendert wird und irgendwo in den Tiefen des Renderbaums jemand etwas tut und die Tests fallen.
  • Die Fähigkeit, den Status der Anwendung zu steuern, geht verloren, da (im übertragenen Sinne) die Fähigkeit, Selektoren / Resolver (insbesondere mit Proxyquire) zu benetzen, verloren geht und das gesamte Gate nass sein muss. Und das ist cool für Unit-Tests.

Wenn es Ihnen so vorkommt, als wären die Probleme ein wenig kompliziert - versuchen Sie, in einem Team zu arbeiten, wenn sich diese Container, die in Ihren Nicht-Containern verwendet werden, in anderen Abteilungen ändern. Infolgedessen sehen Sie und sie sich die Tests an und Sie können nicht verstehen, warum gestern alles es hat funktioniert und jetzt wieder.

Infolgedessen müssen Sie flache verwenden, wodurch alle schädlichen (und unerwarteten) Nebenwirkungen beseitigt werden. Hier ist ein einfaches Beispiel aus dem Artikel „Warum ich immer flach benutze“


Stellen Sie sich vor, Tooltip rendert "?". Wenn Sie darauf klicken, wird der Typ selbst angezeigt.


 import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> } 

Wie teste ich es? Mounten + Klicken + Überprüfen, was sichtbar ist. Dies ist ein Integrationstest, keine Einheit, und die Frage ist, wie Sie auf eine "fremde" Komponente klicken können. Es gibt kein Problem mit Flach, da es kein Gehirn und die "Alien-Komponente" selbst gibt. Aber hier gibt es Köpfe, da Tooltip ein Container ist, während MyComponent praktisch eine Präsentation ist.


 jest.mock('react-cool-tooltip', {default: ({children}) => childlren}); 

Wenn Sie jedoch auf einen Tooltip reagieren, gibt es keine Probleme beim Testen. Die „Komponente“ ist scharf viel dümmer, viel kürzer, viel endlicher geworden .


Letzte Komponente


  • eine Komponente mit einer bekannten Größe, die andere, zuvor bekannte, endgültige Komponenten enthalten kann oder diese überhaupt nicht enthält.
  • enthält keine anderen Behälter, da sie einen unkontrollierten Zustand enthalten und die Größe "erhöhen", d.h. Machen Sie die aktuelle Komponente unendlich .
  • Andernfalls handelt es sich um eine reguläre Präsentationskomponente. Genau wie in der ersten Version von Dans Artikel beschrieben.

Die letzte Komponente ist nur ein Zahnrad, das aus einem großen Mechanismus herausgenommen wurde.


Die ganze Frage ist, wie man es herausnimmt.


Lösung 1 - DI


Mein Favorit ist Dependency Injection. Dan liebt ihn auch . Im Allgemeinen ist dies nicht DI, sondern "Slots". Kurz gesagt - es ist nicht erforderlich, Container in Presentation zu verwenden - sie müssen dort injiziert werden. Und in Tests wird es möglich sein, etwas anderes zu injizieren.


 //    mount     const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); //     shallow,       //     mount ? , ,   wiring? const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> ); 

Dies ist genau der Fall, wenn "Container der Kofferraum sind und Präsentationskomponenten Blätter sind".


Lösung 2 - Grenzen


DI kann oft cool sein. Wahrscheinlich denkt% username% jetzt darüber nach, wie es auf die aktuelle Codebasis angewendet werden kann, und die Lösung ist nicht erfunden ...


In solchen Fällen werden Sie von Borders gerettet.


 const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // //  jest.mock ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> ); 

Hier verwandeln sich anstelle von „Slots“ alle „Übergangspunkte“ einfach in Boundary, wodurch während der Tests alles gerendert wird. Deklarativ genug und genau das, was Sie brauchen, um "die Ausrüstung herauszunehmen".


Lösung 3 - Tier


Rahmen können etwas rau sein, und es ist möglicherweise einfacher, sie etwas intelligenter zu gestalten, indem Sie ein wenig Wissen über Ebenen hinzufügen.


 const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome); 

Unter dem Namen Tier / Layer kann es verschiedene Dinge geben - Feature, Ente, Modul oder nur diese Layer / Tier. Der Punkt ist nicht wichtig, die Hauptsache ist, dass Sie das Zahnrad ziehen können, vielleicht nicht eines, sondern die endgültige Nummer, und irgendwie eine Linie zwischen dem ziehen, was Sie brauchen und dem, was Sie nicht brauchen (für verschiedene Tests ist dies eine andere Grenze).


Und nichts hindert daran, diese Grenzen irgendwie anders zu markieren.


Lösung 4 - Getrennte Bedenken


Wenn die Lösung (per Definition) in der Trennung von Entitäten liegt - was passiert, wenn wir sie nehmen und trennen?


„Container“, die wir so wenig mögen, werden normalerweise als Container bezeichnet . Und wenn nicht, hindert nichts daran, Komponenten jetzt klangvoller zu benennen. Oder sie haben ein bestimmtes Muster in ihrem Namen - Connect (WrappedComonent) oder GraphQL / Query.


Was ist, wenn zur Laufzeit eine Linie zwischen Entitäten anhand eines Namens gezogen wird?


 const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/) 

Plus eine Zeile in den Tests und React-Remock entfernen alle Container, die die Tests stören könnten.


Im Prinzip kann dieser Ansatz verwendet werden, um die Container selbst zu testen - Sie müssen nur alles außer dem ersten Container entfernen .


 import {createElement, remock} from 'react-remock'; //  "" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // ""     <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // "" : null )} </ContainerCondition.Consumer> ) 

Wieder - ein paar Leinen und Zahnräder entfernt.


Insgesamt


Im letzten Jahr wurde das Testen von React-Komponenten komplizierter, insbesondere für das Mounten. Sie müssen alle 10 Anbieter und Kontexte überschreiben, und es wird immer schwieriger, die richtige Komponente im richtigen Stil zu testen. Es gibt zu viele Seile, um sie zu ziehen.
Jemand spuckt und geht in die flache Welt. Jemand winkte bei Unit-Tests mit den Händen und übertrug alles auf Cypress (Gehen wie Gehen!).


Jemand anderes drückt einen Finger auf die Reaktion und sagt, dass dies algebraische Effekte sind und Sie tun können, was Sie wollen. Alle obigen Beispiele beziehen sich im Wesentlichen auf die Verwendung dieser algebraischen Effekte und Verspottungen. Für mich und DI sind das Moki.


PS: Dieser Beitrag wurde als Antwort auf Kommentare in React / RFC über die Tatsache geschrieben, dass das React-Team alles und alle Polymere dort kaputt gemacht hat
PPS: Dieser Beitrag ist eigentlich eine sehr kostenlose Übersetzung eines anderen
PPPS: Um eine echte Isolation zu erreichen, sollten Sie sich im Allgemeinen das Rewiremock ansehen

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


All Articles