Reakt-Prop-Drilling und „God Components“ reduzieren
Erlernen Sie sieben konkrete Refactoring-Muster, um überdimensionierte React-Komponenten zu zerlegen, indem Sie Zustand, Datenabruf, Berechtigungen und Ladelogik isolieren anstelle nur von Dateien zu trennen.
Es gab eine Zeit, in der die Komplexität von React ausschließlich anhand der Anzahl der Zeilen beurteilt wurde. Sobald eine Komponente dreihundert Zeilen überschritt, musste die Toolbar in eine eigene Datei ausgelagert werden. Bei vierhundert Zeilen musste auch die Tabelle extrahiert werden. Die Datei auf höchster Ebene wurde kleiner, doch die Funktionalität selbst war dadurch nicht einfacher zu verstehen. Die Elternkomponente enthielt weiterhin alle Netzwerkanfragen, alle Modale, zahlreiche Formfelder samt deren Validierungsstatus, Überprüfungen dessen, was der aktuelle Benutzer tun durfte, sowie die sich ändernden Zustände, durch die ein Bildschirm beim Laden, Bearbeiten, Speichern – und manchmal auch bei Fehlern – geht.
Was tatsächlich geschah, war eine Umordnung der Bestandteile, ohne dass sich etwas daran änderte, wer das „Haus“ eigentlich besaß.
Dieser Unterschied ist es wert, ihn zu berücksichtigen – schließlich ist allein die Größe nicht das Problem. Ein Komponenten kann zu Recht groß sein, wenn sie ein einheitliches, zusammenhängendes UI-Element darstellt. Ein dichter Editor, ein Dashboard oder eine Berichtsseite können durchaus viel Markup enthalten. Probleme entstehen erst, wenn aus einer Komponente der einzige Ort wird, an dem völlig unzusammenhängende Entscheidungen getroffen werden.
Keines der unten aufgeführten Muster ist an sich schlecht für React. Jedes hat sinnvolle Anwendungsmöglichkeiten in kleineren oder besser durchdachten Umgebungen. Sie werden erst zu einem Nachteil, wenn sie immer wieder in großen Komponenten auftauchen – denn jede solche Verwendung erhöht die Kopplung, vergrößert die betroffene Fläche bei Neuformatierungen und zwingt zu künftigen Änderungen, die den gesamten Bildschirm gleichzeitig berücksichtigen müssen.
Durch das Entfernen dieser Komponenten bleibt man nicht mit einem Haufen sinnloser, kleiner Bestandteile zurück. Stattdessen werden die Strukturen bezüglich Zustand, Datenabruf, Interaktion und Darstellung klarer. Der Code lässt sich leichter ändern, weil weniger Teile der Funktionalität versehentlich miteinander in Konflikt geraten können.
1. Komponenten, die die gesamte Funktionalität weitergegeben haben
Eine Gewohnheit, die auf größeren Bildschirmen ständig auftaucht, besteht darin, große Objekte sowie lange Listen von Callbacks in fast jede Kindkomponente einzubetten.
Stellen Sie sich eine Ergebnistabelle vor, die den aktuellen Benutzer, ein vollständiges Berechtigungsobjekt, die aktiven Filter, die ausgewählte Zeile, ein Ladeflagge, mehrere Mutationsfunktionen, Modal-Setter sowie Benachrichtigungshandler erhält – dazu noch einige Werte, die sie nie direkt berührt. Diese Tabelle leitet anschließend einen Teil davon direkt an Zeilenkomponenten weiter, die sie wiederum an einzelne Zellen und Schaltflächen weiterleiten.
Betrachtet man den Dateibaum, scheint alles gut getrennt zu sein. Doch wenn man sich die tatsächlich zwischen den Komponenten fließenden Daten ansieht, ergibt sich ein anderes Bild: Jedes Kindkomponente ist weiterhin mit dem gesamten Funktionsumfang verbunden.
Dies führt zu zwei unterschiedlichen Problemen. Erstens leidet das Verständnis der Struktur. Ein Komponente, die fünfzehn Props entgegennimmt, ist schwer zu durchschauen, da ihre eigentliche Funktion hinter Details verborgen ist, die eigentlich zum Elternteil gehören. Zweitens breiten sich Änderungen nach außen aus. Das Umbenennen eines einzelnen Berechtigungsfeldes oder die Anpassung der Funktionssignatur einer Aktion bedeutet, mehrere Schichten von Komponenten bearbeiten zu müssen, die ursprünglich keine wirkliche Verantwortung für dieses Verhalten hatten.
Die Lösung besteht darin, umfassende Props, die ganze Funktionalitäten beschreiben, durch enge Verträge zu ersetzen, die genau auf das beschränkt sind, wofür jede Kindkomponente tatsächlich verantwortlich ist. Eine Tabelle benötigt nur Zeilen, den Auswahlzustand sowie einen Callback wie onRowSelected. Ein Aktionenmenü benötigt nur die spezifischen Aktionen, die für ein bestimmtes Datenelement verfügbar sind – nicht das gesamte Berechtigungsmodell samt allen vorhandenen Mutationsfunktionen.
Um dorthin zu gelangen, bedeutet es in der Regel, vor dem Renderen ein kleines View-Modell oder Action-Modell zu erstellen. Dieser zusätzliche Vorbereitungsschritt ist wertvoll, denn er zwingt den Elternteil dazu, den rohen Anwendungsstatus in eine kleinere, speziell dafür konzipierte Schnittstelle umzuwandeln, bevor er etwas weiterleitet.
Ziel ist niemals es, die Anzahl der Props aus reiner Sparsamkeit zu minimieren. Der eigentliche Vorteil besteht darin, dass die Kinder nicht mehr verstehen müssen, wie die gesamte Seite funktioniert. Sie müssen lediglich wissen, welche Daten angezeigt werden sollen und welche Absichten sie nach oben melden müssen.
Ein großer Komponente wird instabil, sobald jedes Nachkommenteil einen Teil der gesamten internen Welt seines Elternteils mit sich trägt. Enge, speziell konzipierte Verträge ermöglichen es den einzelnen UI-Teilen, unabhängig voneinander zu entwickeln, anstatt die gesamte Funktion in jedes Gespräch hineinzuziehen.
2. Jedes Mal neue Objekte und Funktionen bei jedem Render erstellen
Jeder React-Komponente erzeugt beim Renderen natürlich neue Werte. Man übergeben ein Optionsobjekt an eine Kindkomponente, filtert einen Array oder schreibt einen inline-Event-Handler. Meistens spielt das keine Rolle.
Aber innerhalb eines tiefen Komponentenbaums kann eine frisch erstellte Referenz stumm die Memoisierung mehrere Ebenen unterhalb der Komponente, die sie erzeugt hat, stören.
Betrachten Sie eine Kindkomponente, die mit memo gebaut wurde: Sie wird immer wieder gerendert, sobald das von ihr empfangene Objekt bei jeder Übertragung an einen Elternteil eine neue Identität hat – selbst wenn sich die tatsächlichen Feldwerte innerhalb dieses Objekts nie geändert haben. Ein Effect startet erneut, weil sein Abhängigkeitsarray ein neu erstelltes Konfigurationsobjekt enthält. Eine Tabelle berechnet ihre Spalten erneut, weil die Spaltendefinitionen nach einer völlig unabhängigen Änderung des Modal-Zustands neu erstellt wurden.
Der Code kann völlig stabil erscheinen, während dieses Problem verborgen bleibt:
<ResultsTable
columns={[
{ key: "name", label: "Name" },
{ key: "status", label: "Status" },
]}
options={{
selectable: true,
compact: false,
}}
/>
Von der Sichtweise von JavaScript aus sind sowohl das Array als auch die darin enthaltenen Objekte bei jeder Neuansicht völlig neu. Ob das tatsächlich Probleme verursacht, hängt vollständig davon ab, was sie verwendet.
Auch das Einsatz von useMemo und useCallback als allgemeine Lösung ist nicht die richtige Vorgehensweise. Alles in Memoisierung zu packen, fügt lediglich eine weitere Schicht an Komplexität sowie Problemen mit Abhängigkeitsarrays hinzu. Ein besserer Ausgangspunkt ist die Frage, ob der Wert überhaupt innerhalb der Neuansicht erstellt werden muss.
Die statische Konfiguration kann vollständig außerhalb der Komponente platziert werden. Konfigurationen, die gelegentlich ändern, können in einen speziellen Hook verschoben werden. Wenn ein Objekt ausschließlich dazu dient, einige Primitive zusammenzufassen, ist es in der Regel sauberer, die Primitiven direkt weiterzugeben. Event-Handler benötigen nur dann die Stabilisierung mit useCallback, wenn ihre Identität tatsächlich von Bedeutung ist – beispielsweise bei Abonnements, gememorierten Kindkomponenten oder aufwändigen Nachberechnungen weiter flussabwärts.
Ziel ist niemals die referenzielle Reinheit um ihrer selbst willen. Das Erstellen kleiner Werte während des Renderns ist normal und in der Regel unbedenklich. Sorgfalt sollte nur dann aufgewendet werden, wenn die Referenzidentität anderswo im Baum tatsächlich eine Funktion erfüllt.
In einem großen Komponenten kann eine einzige, geringfügige Zustandsaktualisierung dazu führen, dass der Elternteil erneut gerendert wird und dabei ganze Gruppen von Werten neu generiert werden. Wenn jedes Kind eine geänderte Referenz als geänderte Daten betrachtet, verwandelt sich eine kleine lokale Aktualisierung in eine Invalidation der gesamten Seite.
3. Entfernen der einzigen Abfrage, die den gesamten Bildschirm antrieb
Große Seiten beginnen oft mit einer einzigen Anfrage, die dazu dient, alles zu holen, was die Benutzeroberfläche möglicherweise benötigt. Diese eine Antwort enthält Zusammenfassungsmetriken, Tabellenspalten, Filter, verwandte Datensätze, Berechtigungsdaten, aktuelle Historie und sogar Felder, die nur von einem Modalfenster benötigt werden, das der Benutzer vielleicht nie öffnet.
Auf den ersten Blick scheint dieser Ansatz effizient zu sein, da die Seite genau einen Ladezustand und eine eindeutige Datenquelle hat. Doch er bindet außerdem jeden Teil der Seite an den langsamsten und unzuverlässigsten Bestandteil dieser Antwort.
Falls der History-Service versagt, wird die Haupttabelle möglicherweise überhaupt nicht angezeigt. Eine umfangreiche, damit verbundene Sammlung vergrößert die anfängliche Datenmenge – selbst bei Benutzern, die das dafür benötigte Panel niemals öffnen. Zudem zwingt das Erneuern nur eines Abschnitts zu einem vollständigen Neuladen, da die gesamte Seite eine einzige Datengrenze teilt.
Ein besseres Vorgehen besteht darin, diese seitenweiten Anfragen durch Datengrenzen zu ersetzen, die den tatsächlich sichtbaren Inhalten des Bildschirms entsprechen. Der Hauptinhalt wird zuerst geladen. Sekundäre Panels holen ihre eigenen Daten erst dann ab, wenn sie relevant werden. Teure Details werden nur abgerufen, wenn ein Benutzer ein bestimmtes Datensatz öffnet, anstatt standardmäßig in jeder Zeile enthalten zu sein.
Das bedeutet nicht, für jedes unbedeutende Widget eine Netzwerkanfrage zu starten. Eine übermäßige Trennung dieser Art führt zu eigenen Problemen – sequenzielle Anfragen, doppelte Aufrufe sowie inkonsistente Ladezustände. Die wirklich wichtige Grenze ist in der Regel ein Bereich mit eigenem Lebenszyklus und eigener Definition dessen, was „Fehler“ bedeutet.
Ein Zusammenfassungswidget und ein historisches Audit-Log müssen nicht als Einheit erfolgreich oder fehlschlagen. Eine Tabelle und ein selten genutztes Editor-Panel müssen nicht denselben initialen Dateninhalt teilen. Sobald diese Komponenten getrennt werden, bleibt die Seite funktionsfähig, selbst wenn ein optioneller Abschnitt ausfällt.
Der Komponenten selbst wird es außerdem viel einfacher, da er nicht mehr eine riesige, umfangreiche Antwortstruktur modellieren muss. Jede Region erhält den Datenvertrag, den sie tatsächlich benötigt, und die Cache-Invalidierung kann gezielt nur auf die geänderten Datensätze gerichtet werden, anstatt die gesamte Seite zu betreffen.
Große Komponenten neigen dazu, besonders anfällig zu werden, wenn ihr Datenmodell eher durch Bequemlichkeiten auf Seiteebene als durch den natürlichen Lebenszyklus der darin enthaltenen Funktionen bestimmt wird.
4. Entfernung von generischen Komponenten, die von Dutzenden von Flags gesteuert werden
Ein häufiger erster Impuls, um Duplikate zu vermeiden, besteht darin, äußerst konfigurierbare Komponenten zu erstellen. Ein einzelnes Panel kann suchbar, auswählbar, paginiert, editierbar, exportierbar und zusammenfaltbar gemacht werden – alles über eine immer größer werdende Anzahl von booleschen Eigenschaften gesteuert.
Dies erscheint wiederverwendbar, da es scheinbar eine breite Palette an Szenarien abdeckt. In Wirklichkeit bedeutet jedes neue Bildschirmformat lediglich die Hinzufügung einer weiteren Bedingung zu der bestehenden Menge.
<DataPanel
searchable
selectable
showToolbar
allowExport={canExport}
inlineEdit={mode === "admin"}
compact={isInsideModal}
hidePagination={rows.length < 20}
stickyHeader={!isMobile}
/>
Das eigentliche Problem liegt nicht nur in der großen Anzahl an Eigenschaften. Die Flags wirken auf unvorhersehbare Weise miteinander zusammen. Die Inline-Editierung verhält sich anders, sobald der Auswahlmodus aktiv ist. Das kompakte Layout benötigt eigene Logik für die Werkzeugleiste. Ein sticky Header bricht innerhalb eines bestimmten Overflow-Containers ab. Die Anzahl der möglichen Flag-Kombinationen wächst weitaus schneller, als jemand in der Lage ist, sie tatsächlich zu testen.
Die Komponente verwandelt sich stillschweigend in eine zweite Anwendung, die sich innerhalb der ersten versteckt.
Diese flaggesteuerte Wiederverwendung zu ersetzen bedeutet, kleinere, zusammensetzbare Komponenten vorzuziehen sowie dort, wo die Unterschiede erheblich sind, explizit getrennte Varianten zu verwenden. Eine Tabelle kann je nach Bedarf mit einer Werkzeugleiste, einer Seitenzahlungskontrolle und einem Auswahlmechanismus kombiniert werden. Eine editierbare Tabelle kann als eigenständige Funktion existieren, anstatt nur ein weiterer boolescher Zweig innerhalb eines generischen Tabellelements zu sein.
Wo zwei Schnittstellen eine echte zugrundeliegende Struktur teilen, wird diese Struktur direkt wiederverwendet. Wo sie sich lediglich auf einem Screenshot ähneln, aber unterschiedliche Arbeitsabläufe folgen, macht es keinen Sinn mehr, sie durch eine gemeinsame Abstraktion zu zwingen.
Dadurch kehrt eine zuvor versteckte Duplikation zurück, was anfangs wie ein Rückschritt erscheinen kann. In der Praxis ist diese Duplikation in der Regel viel günstiger als die durch sie ersetzte branchbasierte Architektur. Zwei kleine, separate Komponenten können unabhängig voneinander geändert werden, ohne dass jede Änderung gegen Dutzende unzusammenhängender Flaggenkombinationen überprüft werden muss.
Wiederverwendung lohnt sich, wenn sie einen einzigen, stabilen Vertrag schützt. Sie wird riskant, sobald man dazu eine Komponente so erweitert, dass sie heimlich mehrere verschiedene Produkte gleichzeitig darstellt.
5. Entfernen des Modal-Zustands von der Seite, die das Modul geöffnet hat
Große Komponenten fungieren häufig als Modal-Manager. Sie überwachen, ob jedes Dialogfeld geöffnet ist, welches Dokument gerade bearbeitet wird, an welcher Stelle des Ablaufs man sich befindet, ob gerade gespeichert wird und welcher Fehler zuletzt aufgetreten ist.
Oft enthält die Seite selbst nur eine einzige Zeile, die dazu führt, dass das Modalfenster geöffnet wird, doch sie ist dennoch für den gesamten Lebenszyklus dieses Dialogs verantwortlich.
Dadurch entsteht ein Zustand, der auch dann weiterhin aktiv bleibt, wenn das Dialogfenster völlig unsichtbar ist. Es richtig zu schließen bedeutet, ausgewählte Datensätze, Werte aus Feldern für Entwürfe, Validierungsfehler sowie ausstehende Anfragen in einer bestimmten Reihenfolge zu löschen. Das Öffnen eines anderen Dialogs danach birgt das Risiko, versehentlich noch vorhandene Werte aus dem zuvor geöffneten Dialog erneut zu verwenden.
Durch die Behandlung jedes komplexeren Dialogs als eigenständige Funktion statt als bedingtem JSX-Element, das der Seite hinzugefügt wird, ändert sich diese Dynamik. Die Aufgabe der Seite besteht dann darin, festzustellen, auf welchen Datensatz der Benutzer eingreifen möchte. Der Dialog selbst ist für die Entwurfsdaten, die Validierungslogik, die internen Schritte sowie den Lebenszyklus der Übermittlung verantwortlich.
In einigen Fällen reicht es aus, das Dialogfeld nur dann zu laden, wenn es tatsächlich geöffnet ist, um seinen vorübergehenden Zustand automatisch zurückzusetzen. In anderen Fällen muss der Zustand auch nach dem Schließen erhalten bleiben, weshalb er in einen speziellen Entwurfsbereich übertragen wird, anstatt weiterhin mit dem Tabellenzustand und den Filtereinstellungen der Seite verknüpft zu bleiben.
Diese Trennung macht das asynchrone Verhalten außerdem deutlich sicherer. Ein Bearbeitungsdialog kann veraltete Anfragen sofort beim Schließen stornieren oder ignorieren. Eine Speichern-Aktion kann selbst entscheiden, ob der Dialog bei einem Fehler weiterhin geöffnet bleibt. Die Seite muss nicht länger interne Formmechanismen koordinieren, die sie eigentlich nicht versteht.
Die Seite behält weiterhin die Kontrolle über die Verbindung zwischen der Tabelle und dem Dialog – sie weiß, welcher Datensatz ausgewählt ist und was nach einem erfolgreichen Bearbeiten geschehen soll. Doch sie kontrolliert nicht mehr jedes Feld und jede Übergangssituation allein deshalb, weil der Dialog von dieser Seite gestartet wurde.
Eine Modalliste mag visuell über dem Bildschirm angeordnet sein, doch diese visuelle Anordnung bedeutet nicht, dass ihr gesamter Zustandslebenszyklus innerhalb des Komponenten-Elements des Bildschirms ablaufen muss.
6. Genehmigungsprüfungen aus verstreuten JSX-Elementen herausnehmen
Die Autorisierungslogik dringt in der Regel schrittweise in Komponenten ein. Ein Button wird für normale Benutzer versteckt, ein Menüpunkt deaktiviert, sobald ein Datensatz archiviert wird, und ein Abschnitt wird nur für Admins angezeigt.
Diese Bedingungen vermehren sich, weil es mit JSX äußerst einfach ist, weitere Prüfungen hinzuzufügen:
{user.role === "admin" && record.status !== "archived" && (
<DeleteButton />
)}
Sobald eine Komponente zu groß wird, kann sie mehrere leicht unterschiedliche Versionen dessen enthalten, was eigentlich dieselbe Regel sein sollte. Eine Bedingung bestimmt, ob etwas sichtbar ist, eine andere, ob sein Handler ausgelöst wird, und eine dritte, ob ein Menüeintrag ausgegraut ist. Mit der Zeit weichen diese Kopien voneinander ab und stimmen nicht mehr überein.
Dieser Abweichungsprozess ist hauptsächlich ein Korrektheitsbug, schadet aber auch der Lesbarkeit. Die Layout-Markup verheddern sich mit den Geschäftsregeln, und jeder, der die Komponente liest, muss die über den gesamten Baum verstreuten Berechtigungsausdrücke mental auswerten, um zu verstehen, was die Seite tut.
Anstelle von rohen Regelprüfungen im JSX kann man im Voraus eine explizite Liste von Fähigkeiten berechnen:
const capabilities = getRecordCapabilities({
user,
record,
organization,
});
Von dort aus kann die Komponente einfach prüfen, ob dem aktuellen Benutzer das Bearbeiten, Archivieren, Exportieren oder Löschen des jeweiligen Datensatzes gestattet ist. Die Namen beschreiben tatsächliche Produktentscheidungen, anstatt rohe Datenbankfelder oder Rollenzusammenfassungen preiszugeben.
Zur Klarstellung: Dadurch wird die Umsetzung der Sicherheitsregeln nicht auf den Client verlagert. Der Backend bleibt weiterhin die eigentliche Autorisierungsstelle – daran ändert sich nichts. Das Fähigkeitsobjekt im Frontend dient ausschließlich dazu, der Benutzeroberfläche eine einheitliche Quelle der Wahrheit zu bieten, anstatt dieselben Regeln in fünf verschiedenen visuellen Bereichen erneut abzuleiten, die sonst unbemerkt aus dem Gleichgewicht geraten könnten.
Dadurch werden die Regeln selbst viel leichter zu testen. Sie können die Berechtigungslogik anhand verschiedener Rollen, Eigentumskonfigurationen, Statusseiten und Organisationseinstellungen überprüfen, ohne den gesamten Komponentenbaum darzustellen. Wenn sich die zugrunde liegende Richtlinie ändert, muss in der Regel nicht einmal die umgebende Komponente geändert werden.
Große JSX-Bäume werden anfällig, wenn dort gleichzeitig Geschäftsregeln ad hoc entwickelt werden. Das Rendern sollte hauptsächlich darin bestehen, bereits getroffene Entscheidungen zu nutzen, anstatt diese in jedem bedingten Zweig von Grund auf neu zu erstellen.
7. Aufgabe der Seitenweiten Ladevorgänge und Fehlerflaggen
Die meisten großen Komponenten beginnen unschuldig mit einem einzigen isLoading-Wert und einem einzigen error-Wert. Das ist in Ordnung, solange die Seite nur eine Aufgabe ausführt. Doch wenn sich eine Funktion weiterentwickelt, versuchen dieselben beiden Flagge-Werte schließlich, gleichzeitig mehrere unzusammenhängende Aktivitäten zu beschreiben.
Eine Seite kann beispielsweise ihre Anfangsdaten abrufen, eine Tabelle im Hintergrund aktualisieren, ein Formular speichern, eine Zeile löschen und einen Bericht exportieren – manchmal alles in derselben Sitzung. Ein gemeinsames Lade-Boolean kann nicht angeben, welche dieser Operationen gerade ausgeführt wird. Wenn eine Anfrage abgeschlossen ist und das Flagge auf false setzt, läuft möglicherweise bereits eine völlig andere Anfrage weiter. Ein Fehler durch eine Änderung überschreibt den Fehler, der ursprünglich die Anfangsladung der Seite beschreiben sollte. Die gesamte Benutzeroberfläche blockiert einfach nur, weil zufällig eine unzusammenhängende Hintergrundaktualisierung läuft.
Ein besseres Vorgehen besteht darin, diese seitenweiten Statusflaggen durch einen Status zu ersetzen, der zur spezifischen Aktion gehört, die er beschreibt.
Das bedeutet, dass die anfängliche Abfrage geladen werden kann, während die vorhandene Tabelle weiterhin vollständig sichtbar und interaktiv bleibt. Eine einzelne Zeile kann mitten in der Löschung sein, ohne dass alle anderen Zeilen auf der Seite deaktiviert werden. Der Editor kann sich im Absenden befinden, während die Filter der Seite weiterhin nutzbar sind. Eine Exportfunktion kann ihren eigenen Fortschrittsanzeiger anzeigen, ohne zu implizieren, dass der gesamte Bildschirm nicht mehr verfügbar ist.
Das Ergebnis sind individuellere Statuswerte, wobei jeder von ihnen eine eindeutige Bedeutung hat. Im Code muss nichts raten, worauf sich ein generischer isLoading zu einem bestimmten Zeitpunkt tatsächlich bezieht.
Dieselbe Logik gilt auch für Fehler: Es ist nicht notwendig, dass alle Fehler auf ein einziges oberstes Fehlerrichtungsfenster geleitet werden. Ein fehlerhafter, optionaler Bereich kann an seiner Stelle seine eigene Wiederholungsoption anzeigen. Ein Mutationfehler kann auf dem Workflow bleiben, der ihn ausgelöst hat. Die Hauptseite selbst wird nur dann unzugänglich, wenn die zur Anzeige dieser Seite erforderlichen Daten nicht geladen werden konnten.
Dadurch entsteht eine widerstandsfähigere Benutzeroberfläche sowie ein Komponentenmodell, das leichter zu verstehen ist. Der Status wird nicht mehr einfach deshalb als global angesehen, weil die Aktion zufällig innerhalb eines großen Seitenkomponenten stattfindet.
Eine große Komponente wirkt oft instabil, weil mehrere unterschiedliche Workflows gezwungen sind, ein einziges Signal zu teilen. Jeder Workflow erhält seinen eigenen Status, wodurch die Benutzeroberfläche stets genau angeben kann, was zu jedem Zeitpunkt funktioniert und was nicht.
Es ging nie nur um kleinere Dateien
Sobald diese Muster entfernt sind, werden viele Komponenten tatsächlich kürzer – doch das ist ein Nebeneffekt, nicht das eigentliche Ziel.
Der wirkliche Vorteil besteht darin, dass innerhalb einer einzigen Render-Grenze weniger unzusammenhängende Entscheidungen getroffen werden. Kindkomponenten erhalten Verträge, die auf ihren eigenen Verantwortungsbereich beschränkt sind, anstatt den Zustand der gesamten Funktion. Dadurch wird verhindert, dass bei jeder Renderung ganze Unterbäume stillschweigend ungültig gemacht werden. Die Daten werden entsprechend dem, was tatsächlich auf dem Bildschirm sichtbar ist, abgerufen – anstatt durch eine abfragebasierte Seite. Wiederverwendbare Komponenten müssen nicht mehr für jede mögliche Variante, die jemals benötigt wurde, ein neues Flag speichern.
Modale übernehmen die Kontrolle über ihren eigenen Zustand. Berechtigungsregeln werden zu benannten Fähigkeiten statt zu inline-Bedingungen. Lade- und Fehlerzustände gehören zu den spezifischen Operationen, die sie erzeugen.
Einige Benutzeroberflächen bleiben groß, einfach weil die Schnittstelle selbst tatsächlich umfangreich ist – und das ist in Ordnung. Der Code wird leichter zu handhaben, nicht weil er kleiner geworden ist, sondern weil seine Größe eine echte, sichtbare Struktur widerspiegelt anstelle einer versteckten Koordinationslogik.
Zu diesem Zeitpunkt ist die richtige Frage zu einem React-Komponenten nicht, wie viele Zeilen sie enthält. Vielmehr geht es darum, wie viele unabhängige Faktoren dazu führen könnten, dass sie sich ändert. Wenn das Bearbeiten im Editor bedeutet, man muss auch die Abfragen der Tabelle, den Exportzustand, das Berechtigungssystem sowie alle Modale auf der Seite verstehen, dann liegt das Problem nicht im Formatieren oder bei der Dateilänge. Die Verantwortungsgrenzen sind einfach an der falschen Stelle festgelegt.
Große React-Komponenten bleiben handhabbar, sobald sie aufhören, selbst versuchen zu müssen, wie die gesamte Anwendung zu funktionieren.
Ziel war niemals, eine Datei weiter aufzuteilen, bis jede Funktion winzig ist.
Ziel ist es zu gewährleisten, dass jedes Teil einer Funktion nur von den Entscheidungen weiß, für die es tatsächlich verantwortlich ist.
Verwandte Artikel
- Zehn versteckte React-Komponentenfehler, die moderne Apps verlangsamen — Erfahren Sie zehn häufige Fehler bei React-Komponenten – von Lücken im semantischen HTML bis hin zu fehlender Memoisierung – sowie die notwendigen Lösungen, um Apps im Jahr 2026 schnell, zugänglich und fehlerfrei zu halten.
- Behebung von React-Prop-Overload durch Komposition und Slots — Erfahren Sie, warum konfigurationsintensive React-Props zu Wartungsaufwänden führen, und wie Inversion of Control, Komposition sowie Slots wirklich wiederverwendbare Komponenten erzeugen.