Ein neuer Ansatz zur Reaktion auf das Komponentendesign

Im Jahr 2015 schrieb Dan Abramov einen Artikel, Presentational and Container Components, den einige React-Neulinge als Gebote missverstanden haben. Tatsächlich bin ich selbst auf den Artikel gestoßen und viele andere haben seine Lehren wiederholt, und ich dachte, dies muss der beste Weg sein, um Bedenken zwischen Komponenten zu trennen .

Dan Abramov selbst wandte sich später an die Community, weil er an den von ihm skizzierten Designmustern festhielt.

Seit über einem Jahr arbeite ich mit React zusammen und bin auf meine eigenen Designmuster gestoßen. Hier werde ich versuchen, sie zu formalisieren. Nehmen Sie diese Ideen mit einem Körnchen Salz, es sind nur meine eigenen Beobachtungen, die ich als konstruktiv empfunden habe.

Flucht vor der Dichotomie

Seit langem werden Komponenten allgemein als intelligent oder dumm, als Container oder als Präsentation, als zustandsbehaftet oder zustandslos, rein oder unrein eingestuft. Es gibt eine Menge Terminologie, aber alle bedeuten ungefähr dasselbe. Intelligente Komponenten wissen, wie Sie Ihre Anwendung zusammenfügen, und dumme Komponenten nehmen nur Daten auf, um sie dem Endbenutzer zu präsentieren. Dies ist eine nützliche Unterscheidung, aber es ist wirklich nicht so, wie ich beim Entwerfen von Komponenten denke.

Das Problem mit der Denkweise Container vs Presentational besteht darin, dass es zu schwierig ist, die Verantwortlichkeiten der Komponenten in Bezug auf Zustand, Logik und andere Aspekte des Innenlebens einer Komponente zu definieren.

Das Komponentendesign wird besser angegangen, indem die Implementierungsdetails verschoben und in Bezug auf Komponentenschnittstellen gedacht werden . Es ist besonders wichtig zu überlegen, welche Art von Anpassungen eine Komponente zulassen sollte und welche impliziten und expliziten Abhängigkeiten eine Komponente enthalten sollte.

Einführung in die Trichotomie

Trichotomie? Ist das überhaupt ein Wort? Ich weiß es nicht, aber du kommst auf die Idee. Ich stelle mir React-Komponenten so vor, als würden sie in einen von drei Behältern fallen.

Universelle Komponenten

Dies sind Komponenten, die in jeder Anwendung mehrfach verwendet werden können .

Diese Komponenten:

  • Sollte wiederverwendbar sein
  • Sollte sehr anpassbar sein
  • Sollte keinen anwendungsspezifischen Code kennen, einschließlich Modelle, Geschäfte, Dienste usw.
  • Sollte die Abhängigkeit von Bibliotheken von Drittanbietern minimieren
  • Sollte selten direkt in Ihrer Anwendung verwendet werden
  • Sollte als Bausteine ​​für globale Komponenten verwendet werden
  • Kann mit dem Suffix "Base" enden (z. B. ButtonBase, ImageBase)

Dies sind grundlegende Komponenten, die anwendungsunabhängig sind und nicht unbedingt direkt in Ihren View-Komponenten verwendet werden müssen, da sie häufig zu anpassbar sind. Die direkte Verwendung in Ihren View-Komponenten würde viel Kopieren und Einfügen derselben Kesselplatte bedeuten. Sie würden auch riskieren, dass Entwickler die hochgradig anpassbaren Eigenschaften der Komponenten so missbrauchen, dass eine inkonsistente Erfahrung in Ihrer Anwendung entsteht.

Globale Komponenten

Dies sind Komponenten, die in einer Anwendung mehrfach verwendet werden können .

Diese Komponenten:

  • Sollte wiederverwendbar sein
  • Sollte minimal anpassbar sein
  • Kann anwendungsspezifischen Code verwenden
  • Sollte universelle Komponenten implementieren und deren Anpassbarkeit einschränken
  • Sollte als Bausteine ​​für View-Komponenten verwendet werden
  • Verknüpfen Sie häufig eins zu eins mit Modellinstanzen (z. B. DogListItem, CatCard).

Diese Komponenten können in Ihrer Anwendung wiederverwendet werden, können jedoch nicht einfach auf andere Anwendungen übertragen werden, da sie von der Anwendungslogik abhängen. Dies sind die Bausteine ​​für View-Komponenten und andere globale Komponenten.

Sie sollten minimal anpassbar sein, um die Konsistenz in Ihrer Anwendung sicherzustellen. Anwendungen sollten nicht dreißig verschiedene Tastenvarianten haben, sondern eine Handvoll verschiedener Tastenvarianten. Dies sollte erzwungen werden, indem eine hochgradig anpassbare Universal ButtonBase-Komponente verwendet und Stile und Funktionen in Form einer Global Button-Komponente eingebrannt werden. Globale Komponenten nehmen häufig eine andere Form als Repräsentation von Domänenmodelldaten an.

Komponenten anzeigen

Dies sind Komponenten, die in Ihrer Anwendung nur einmal verwendet werden .

Diese Komponenten:

  • Sollte nicht über die Wiederverwendbarkeit besorgt sein
  • Sind wahrscheinlich Staat zu verwalten
  • Erhalte minimale Requisiten
  • Sollte globale Komponenten (und möglicherweise universelle Komponenten) zusammenbinden
  • Oft lösen Anwendung Routen
  • Unterhalten Sie häufig ein spezielles Grundstück mit Ansichtsfenstern
  • Haben oft eine hohe Anzahl von Abhängigkeiten
  • Sollte Bausteine ​​für Ihre Anwendung sein

Dies sind die Komponenten Ihrer Anwendung auf höchster Ebene, die wiederverwendbare Komponenten und sogar andere Ansichten zusammenkleben. Dies sind häufig die Komponenten, die Routen auflösen und möglicherweise in Form von Komponenten auf Seitenebene angezeigt werden. Sie sind schwer im Zustand und leicht in Requisiten. Dies würde Dan Abramov als Containerkomponenten betrachten.

Der PromiseButton

Werfen wir einen Blick auf die universellen und globalen Implementierungen eines Versprechen-Buttons und sehen, wie sie miteinander verglichen werden. Eine Versprechen-Schaltfläche verhält sich wie eine normale Schaltfläche, es sei denn, der onClick-Handler gibt ein Versprechen zurück. Im Falle eines zurückgegebenen Versprechens kann die Schaltfläche Inhalte basierend auf dem Versprechungsstatus bedingt rendern.

Beachten Sie, wie wir mit der PromiseButtonBase steuern können, was zu jedem Zeitpunkt im Versprechen-Lebenszyklus gerendert werden soll, während der PromiseButton im ausstehenden Zustand im blaugrünen PulseLoader backt. Jedes Mal, wenn wir den PromiseButton verwenden, ist eine blaugrüne Ladeanimation garantiert, und wir müssen uns nicht darum kümmern, diesen Code zu duplizieren oder ein inkonsistentes Ladeerlebnis zu bieten, indem wir mehrere Ladeanimationen mit mehreren Farben in unsere Anwendung aufnehmen. Die PromiseButtonBase ist anpassbar, aber die PromiseButton ist restriktiv.

Verzeichnisaufbau

The following illustrates how we might organize components following this pattern.

App/ App.js Views/ DogListView/ Global/ Models/ Dog/ DogListItem/ Image/ PromiseButton/ Universal/ ImageBase/ PromiseButtonBase/

Component Dependencies

Below illustrates how the above components depend on one another.

/* App.js */ import { DogListView } from './Views' /* DogListView.js */ import { DogListItem } from 'App/Global/Models/Dog' /* DogListItem.js */ import Image from '../../Image', import PromiseButton from '../../PromiseButton' /* Image.js */ import { ImageBase } from 'Universal' /* PromiseButton.js */ import { PromiseButtonBase } from 'Universal'

Our View component depends on a Global component and our Global components depend on other Global components as well as Universal components. This dependency flow will be pretty common. Notice also the use of absolute and relative imports. It’s nice to use relative imports when pulling in dependencies that reside within the same module. Also, it’s nice to use absolute imports when pulling in dependencies across modules or when your directory structure is deeply nested or frequently changing.

The problem with the Container vs Presentational model is that it tries too hard to define component responsibilities in terms of component inner-workings. The key takeaway is to view component design in terms of component interfaces. What matters less is the implementation that allows the component to satisfy its contract. It’s important to think about what kind of customizations a component should allow and what kind of implicit and explicit dependencies a component should include.

If you’ve found these thoughts helpful and would like to see more of my ideas, feel free to check out this repo which I use to maintain my thoughts and best practices for writing React/Redux apps.