Hier sind ein paar Lektionen, die ich beim Erstellen von React-Komponenten gelernt habe, wie man es *nicht* tut. Das sind Dinge, auf die ich in den letzten Monaten gestoßen bin und von denen ich dachte, sie könnten für Sie interessant sein, wenn Sie an einem Designsystem arbeiten, insbesondere an einem mit vielen technischen Altentscheidungen und viel technischer Schuld im Hintergrund.
Lektion 1: Vermeiden Sie Kindkomponenten, so gut Sie können
Eine Sache bei der Arbeit an einem großen Designsystem mit vielen Komponenten ist, dass das folgende Muster sehr schnell problematisch wird.
<Card>
<Card.Header>Title</Card.Header>
<Card.Body><p>This is some content</p></Card.Body>
</Card>
Die problematischen Teile sind diese Kindkomponenten, Card.Body und Card.Header. Dieses Beispiel ist nicht schlimm, da die Dinge relativ einfach sind – es wird verrückt, wenn Komponenten komplexer werden. Zum Beispiel kann jede Kindkomponente eine ganze Reihe komplexer Props haben, die miteinander interferieren.
Einer meiner größten Schmerzpunkte sind unsere Formular-Komponenten. Nehmen Sie das hier
<Form>
<Input />
<Form.Actions>
<Button>Submit</Button>
<Button>Cancel</Button>
</Form.Actions>
</Form>
Ich vereinfache die Dinge natürlich erheblich, aber jedes Mal, wenn ein Ingenieur zwei Buttons nebeneinander platzieren möchte, importiert er Form.Actions, auch wenn keine Form auf der Seite vorhanden ist. Das bedeutete, dass alles innerhalb der Form-Komponente importiert wurde, was letztendlich schlecht für die Leistung ist. Es stellt sich auch als schlechte Systemdesign-Implementierung heraus.
Das macht die Dokumentation von Komponenten auch extra schwierig, da Sie dann sicherstellen müssen, dass jede dieser Kindkomponenten ebenfalls dokumentiert wird.
Anstatt also Form.Actions zu einer Kindkomponente zu machen, hätten wir sie zu einer brandneuen Komponente machen sollen, einfach: FormActions (oder vielleicht etwas mit einem besseren Namen wie ButtonGroup). Auf diese Weise müssen wir Form nicht ständig importieren und können layoutbasierte Komponenten von anderen trennen.
Ich habe meine Lektion gelernt. Von nun an werde ich Kindkomponenten, wo immer ich kann, komplett vermeiden.
Lektion 2: Stellen Sie sicher, dass Ihre Props nicht miteinander kollidieren
Mandy Michael hat einen großartigen Artikel darüber geschrieben, wie Props aufeinanderprallen können und zu verwirrenden Konflikten führen können, wie dieses TypeScript-Beispiel.
interface Props {
hideMedia?: boolean
mediaIsEdgeToEdge?: boolean
mediaFullHeight?: boolean
videoInline?: boolean
}
Mandy schreibt
Der Zweck dieser Props ist es, die Art und Weise zu ändern, wie das Bild oder Video in der Karte gerendert wird, oder ob das Medium überhaupt gerendert wird. Das Problem bei der separaten Definition ist, dass man am Ende eine Reihe von Flags hat, die Komponentenfunktionen umschalten, von denen viele sich gegenseitig ausschließen. Zum Beispiel kann man kein Bild haben, das die Ränder ausfüllt, wenn es auch versteckt ist.
Dies war definitiv ein Problem für viele der Komponenten, die wir in den Designsystemen meines Teams geerbt haben. Es gab eine Reihe von Komponenten, bei denen boolesche Props eine Komponente auf allerlei seltsame und unerwartete Weise verhalten ließen. Wir hatten sogar alle möglichen Fehler in unserer Card-Komponente während der Entwicklung, weil die Ingenieure nicht wussten, welche Props sie für einen bestimmten Effekt ein- und ausschalten sollten!
Mandy schlägt folgende Lösung vor
type MediaMode = 'hidden'| 'edgeToEdge' | 'fullHeight'
interface Props {
mediaMode: 'hidden'| 'edgeToEdge' | 'fullHeight'
}
Kurz gesagt: Wenn wir all diese aufkeimenden Optionen kombinieren, haben wir eine viel sauberere API, die leicht erweiterbar ist und weniger wahrscheinlich zu Verwirrung in der Zukunft führt.
Das war's! Ich wollte nur eine kurze Notiz zu diesen beiden Lektionen machen. Hier ist meine Frage an Sie: Was haben Sie beim Erstellen von Komponenten oder bei der Arbeit an Designsystemen gelernt?
Ich liebe diesen Beitrag! Wir haben Kindkomponenten (wir nennen sie Subkomponenten) für unser System ziemlich gut funktionieren lassen, aber wir vermeiden einige der von Ihnen angesprochenen Probleme, indem wir eine Regel haben, wann wir sie verwenden – Subkomponenten dürfen nur als Kind der übergeordneten Root-Komponente gerendert werden. Das stimmt ziemlich gut mit der Verwendung von React Context überein.
Ein Beispiel ist unsere SegmentedControl-Komponente (https://sproutsocial.com/seeds/components/segmented-control), die eine Reihe von Optionen in Form einer
SegmentedControl.ItemSubkomponente rendert. Die übergeordnete Komponente verfolgt den ausgewählten Wert des Controls, und die Items lesen diesen Wert aus dem übergeordneten Kontext aus, um ihren aktiven Zustand zu bestimmen. Die API sieht dann ungefähr so auswas sich für mich ziemlich gut anfühlt. Die Einhaltung dieser Regel, Subkomponenten niemals außerhalb der übergeordneten Komponente zu verwenden, hilft uns auch, das Problem des „Importierens einer vollständigen Komponente nur für eine Subkomponente“ zu vermeiden.
Eine Sache, die ich gelernt habe, ist, die Komponenten-API einfach zu halten – d. h. die Anzahl der übergebenen Props.
Am Beispiel einer Button-Komponente...
Sie könnten eine einzige Button-Komponente haben, deren Props bestimmen, ob es sich um einen reinen Text-Button handelt, oder ob er auch ein Icon hat (mit allen zusätzlichen Varianten der Icon-Positionierung), oder ob es sich nur um einen Icon-Button handelt. Außerdem benötigen Sie alle Standard-Props für Größe, Farbe, Klick-Handler, deaktivierten Zustand usw. Viele Props.
Oder haben Sie separate, einfachere Komponenten… z. B. TextButton, TextIconButton, IconButton.
Diese können dann Typen für Größe, Farbe, Handler, Deaktivierung teilen/erweitern.
Ich würde gerne reine Text-Buttons von Buttons mit Text und Icon trennen, aber in meinem Fall gibt es Zeiten, in denen ich nur ein Icon auf Mobilgeräten und Text und Icon auf größeren Geräten anzeigen muss… Oder ein Icon, das auf Mobilgeräten links schwebt, und dasselbe Icon, aber als eigenständiger Block, der den Text im Hochformat nach unten und im Querformat nach oben verschiebt. Wenn ich die Komponenten also trenne, müsste ich jedem, der mit ihnen arbeitet, sagen, dass er je nach Breakpoint einen anderen ein- oder ausblenden soll. Was ich stattdessen mache, ist eine Icon-Prop, die entweder ein Konfigurationsobjekt akzeptiert, um all das einzurichten, oder nur das Icon selbst, falls sie das nicht benötigen.
Enrique, sicherlich können Sie CSS (Media Queries) verwenden, um Text auf Mobilgeräten einfach auszublenden und Flexbox mit Flex-Direction zu verwenden, um die Icon-Position bei Bedarf zu ändern.
Und wenn Sie kein CSS verwenden möchten, erkennen Sie Mobilgeräte (über die Bildschirmbreite) in React und rendern das Icon bedingt in JSX, wenn es keine mobile Größe hat usw.
Das ist tatsächlich das, was ich größtenteils im Hintergrund mache. Entweder nur atomare Klassen setzen/entfernen, die ein Element bei bestimmten Bildschirmgrößen ein- oder ausblenden, oder sie bedingt aus dem DOM entfernen. Die Nutzer meiner Komponentenbibliothek müssen nur entscheiden, bei welcher Größe sie ein-/ausblenden, verschieben usw. auf einer vordefinierten Reihe von Breakpoints.
Diese Frameworks sind immer noch ein MESS zum Arbeiten. Was bringt es, wenn wir die Anzahl der Kindkomponenten einschränken müssen? Das bedeutet, dass sie schlecht konzipiert sind, Punkt.
Wow, dieser Artikel hat mir wirklich gefallen! Ich wusste ehrlich gesagt nicht, dass Kindelemente die Ausführung meines Codes beeinträchtigen können. Ich bin ziemlich neu im Programmieren, ich habe nicht viel Erfahrung mit großen komplexen Projekten. Ich hatte wirklich nicht bedacht, dass mein Code nachlässig sein und zu Problemen führen könnte. Vielen Dank für diesen wirklich nützlichen Tipp.