Um Schnittstellen zu erstellen, empfiehlt React die Verwendung von Bibliotheken für die Kompositions- und Statusverwaltung, um Komponentenhierarchien zu erstellen. Bei komplexen Kompositionsmustern treten jedoch folgende Probleme auf:
- Untergeordnete Elemente müssen unnötig strukturiert werden
- Oder geben Sie sie als Requisiten weiter, was die Lesbarkeit, Semantik und Struktur des Codes erschwert
Für die meisten Entwickler ist das Problem möglicherweise nicht offensichtlich, und sie werfen es auf die Ebene der Statusverwaltung. Dies wird auch in der React-Dokumentation erläutert :
Wenn Sie einige Requisiten nicht mehr auf viele Ebenen übertragen möchten, ist die Komponentenzusammenstellung in der Regel eine einfachere Lösung als der Kontext.
Dokumentation reagieren Kontext.
Wenn wir dem Link folgen, sehen wir ein weiteres Argument:
Auf Facebook verwenden wir React in Tausenden von Komponenten, und wir haben keine Fälle gefunden, in denen wir die Erstellung von Komponentenvererbungshierarchien empfehlen würden.
Dokumentation reagieren Zusammensetzung versus Vererbung.
Wenn jeder, der das Tool verwendet, die Dokumentation gelesen und die Autorität der Autoren anerkannt hätte, wäre dies natürlich nicht der Fall gewesen. Daher analysieren wir die Probleme bestehender Ansätze.
Muster Nr. 1 - Direkt gesteuerte Komponenten

Ich habe mit dieser Lösung begonnen, weil das Vue-Framework diesen Ansatz empfiehlt. Wir nehmen die Datenstruktur, die im Fall des Formulars von der Unterlage oder dem Design herrührt. Weiterleiten an unsere Komponente - zum Beispiel an eine Filmkarte:
const MovieCard = (props) => { const {title, genre, description, rating, image} = props.data; return ( <div> <img href={image} /> <div><h2>{title}</h2></div> <div><p>{genre}</p></div> <div><p>{description}</p></div> <div><h1>{rating}</h1></div> </div> ) }
Halt an Die unendliche Erweiterung der Bauteilanforderungen ist uns bereits bekannt. Plötzlich hat der Titel einen Link zur Filmkritik? Und das Genre - für die besten Filme daraus? Füge jetzt nicht hinzu:
const MovieCard = (props) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> <div><h1>{rating}</h1></div> <div><p>{description}</p></div> </div> ) }
Wir werden uns also in Zukunft vor Problemen schützen, aber die Tür für einen Nullpunktfehler öffnen. Anfangs konnten wir Strukturen direkt von hinten werfen:
<MovieCard data={res.data} />
Jetzt müssen Sie jedes Mal alle Informationen duplizieren:
<MovieCard data={{ title: {res.title}, description: {res.description}, rating: {res.rating}, image: {res.imageHref} }} />
Wir haben jedoch das Genre vergessen - und die Komponente ist gefallen. Und wenn Fehlerbegrenzer nicht gesetzt wurden, dann ist die ganze Anwendung dabei.
TypeScript kommt zur Rettung. Wir vereinfachen das Schema, indem wir die Karte und die Elemente, die sie verwenden, umgestalten. Zum Glück wird alles im Editor oder während der Montage hervorgehoben:
interface IMovieCardElement { text?: string; } interface IMovieCardImage { imageHref?: string; } interface IMovieCardProps { title: IMovieCardElement; description: IMovieCardElement; rating: IMovieCardElement; genre: IMovieCardElement; image: IMovieCardImage; } ... const {title: {text: title}, description: {text: description}, rating: {text: rating}, genre: {text: genre}, image: {imageHref} } = props.data;
Um Zeit zu sparen, rollen wir die Daten immer noch "wie gewünscht" oder "als IMovieCardProps". Was fällt aus? Wir haben bereits dreimal (wenn an einer Stelle verwendet) eine Datenstruktur beschrieben. Und was haben wir? Eine Komponente, die immer noch nicht geändert werden kann. Eine Komponente, die möglicherweise die gesamte Anwendung zum Absturz bringen kann.
Es ist Zeit, diese Komponente wiederzuverwenden. Bewertung wird nicht mehr benötigt. Wir haben zwei Möglichkeiten:
Platziere die Requisite ohne Bewertung, wo immer eine Bewertung benötigt wird
const MovieCard = ({withoutRating, ...props}) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { withoutRating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) }
Schnell, aber wir stapeln Requisiten und bauen eine vierte Datenstruktur auf.
Bewertung in IMovieCardProps optional vornehmen. Vergessen Sie nicht, es standardmäßig zu einem leeren Objekt zu machen
const MovieCard = ({data, ...props}) => { const {title: {text: title}, description: {text: description}, rating: {text: rating} = {}, genre: {text: genre}, image: {imageHref} } = data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { data.rating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) }
Schwieriger, aber es wird schwierig, den Code zu lesen. Wiederholen Sie dies zum vierten Mal . Die Kontrolle über die Komponente ist nicht offensichtlich, da sie durch die Datenstruktur undurchsichtig gesteuert wird. Angenommen, wir wurden gebeten, die notorische Bewertung als Link zu kennzeichnen, aber nicht überall:
rating: {text: rating, url: ratingUrl} = {}, ... { data.rating && data.rating.url ? <div>><h1><a href={ratingUrl}{rating}</a></h1></div> : <div><h1>{rating}</h1></div> }
Und hier stoßen wir auf die komplexe Logik, die die undurchsichtige Datenstruktur vorgibt.
Muster Nr. 2 - Komponenten mit eigenem Zustand und Reduzierstücken

Gleichzeitig ein seltsamer und beliebter Ansatz. Ich habe es verwendet, als ich anfing, mit React zu arbeiten, und die JSX-Funktionalität in Vue fehlte. Mehr als einmal habe ich von Entwicklern bei mitaps gehört, dass Sie mit diesem Ansatz allgemeinere Datenstrukturen überspringen können:
- Eine Komponente kann viele Datenstrukturen annehmen, das Layout bleibt gleich
- Beim Empfang werden die Daten entsprechend dem gewünschten Szenario verarbeitet.
- Die Daten werden im Komponentenzustand gespeichert, um den Reduzierer nicht bei jedem Rendern zu starten (optional).
Das Problem der Opazität (1) wird natürlich durch das Problem der logischen Überladung (2) und die Hinzufügung des Zustands zur endgültigen Komponente (3) ergänzt.
Die letzte (3) wird durch die innere Sicherheit des Objekts bestimmt. Das heißt, wir überprüfen Objekte gründlich durch lodash.isEqual. Wenn das Ereignis vorgerückt oder JSON.stringify ist, fängt alles gerade erst an. Sie können auch einen Zeitstempel hinzufügen und prüfen, ob alles verloren geht. Das Speichern oder Merken ist nicht erforderlich, da die Optimierung aufgrund der Komplexität der Berechnung komplizierter sein kann als eine Reduzierung.
Daten werden mit dem Namen des Skripts (normalerweise eine Zeichenfolge) ausgegeben:
<MovieCard data={{ type: 'withoutRating', data: res.data, }} />
Schreiben Sie nun die Komponente:
const MovieCard = ({data}) => { const card = reduceData(data.type, data.data); return ( <div> <img href={card.imageHref} /> <div><h2>{card.name}</h2></div> <div><p>{card.genre}</p></div> { card.withoutRating && <div><h1>{card.rating}</h1></div> } <div><p>{card.description}</p></div> </div> ) }
Und die Logik:
const reduceData = (type, data) = { switch (type) { case 'withoutRating': return { title: {data.title}, description: {data.description}, rating: {data.rating}, genre: {data.genre}, image: {data.imageHref} withoutRating: true, }; ... } };
In diesem Schritt gibt es mehrere Probleme:
- Durch Hinzufügen einer Logikebene verlieren wir schließlich die direkte Verbindung zwischen Daten und Anzeige
- Doppelte Logik für jeden Fall bedeutet, dass in dem Fall, in dem alle Karten eine Altersfreigabe benötigen, diese in jedem Reduzierer registriert werden muss
- Verbleibende andere Probleme aus Schritt 1
Muster Nr. 3 - Übertragen von Anzeigelogik und Daten an die Statusverwaltung

Hier verzichten wir auf den Datenbus zum Aufbau von Schnittstellen, der React heißt. Wir verwenden Technologie mit unserem eigenen Logikmodell. Dies ist wahrscheinlich die gebräuchlichste Methode, um Anwendungen auf React zu erstellen, obwohl Sie in der Anleitung gewarnt werden, den Kontext nicht auf diese Weise zu verwenden.
Verwenden Sie ähnliche Tools, wenn React nicht genügend Tools bereitstellt, z. B. beim Routing. Höchstwahrscheinlich verwenden Sie einen Reaktionsrouter. In diesem Fall wäre es sinnvoller, den Kontext zu verwenden, anstatt den Rückruf von der Komponente jeder Route der obersten Ebene weiterzuleiten, um die Sitzung an alle Seiten weiterzuleiten. React verfügt über keine separate Abstraktion für asynchrone Aktionen, die nicht in der Javascript-Sprache verfügbar sind.
Es scheint, dass es ein Plus gibt: Wir können die Logik in zukünftigen Versionen der Anwendung wiederverwenden. Aber das ist ein Scherz. Zum einen ist es an die API gebunden, zum anderen an die Struktur der Anwendung, und die Logik stellt diese Verbindung her. Wenn Sie eines der Teile ändern, muss es neu geschrieben werden.
Lösung: Muster Nr. 4 - Zusammensetzung

Die Kompositionsmethode ist offensichtlich, wenn Sie die folgenden Prinzipien befolgen (abgesehen von einem ähnlichen Ansatz in Design Patterns ):
- Frontend-Entwicklung - Entwicklung von Benutzeroberflächen - verwendet HTML für das Layout
- Javascript wird zum Empfangen, Senden und Verarbeiten von Daten verwendet.
Übertragen Sie daher so früh wie möglich Daten von einer Domäne in eine andere. React verwendet die JSX-Abstraktion zum Schablonieren von HTML, verwendet jedoch eine Reihe von createElement-Methoden. Das heißt, JSX- und React-Komponenten, die auch JSX-Elemente sind, sollten als Anzeige- und Verhaltensmethode behandelt werden und nicht als Transformation und Verarbeitung von Daten, die auf einer separaten Ebene erfolgen müssen.
In diesem Schritt wenden viele die oben aufgeführten Methoden an, lösen jedoch nicht das Hauptproblem des Erweiterns und Änderns der Anzeige von Komponenten. Wie das geht, lesen die Autoren der Bibliothek in der Dokumentation:
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
Das heißt, als Parameter werden anstelle von Zeichenfolgen, Zahlen und Booleschen Typen vorgefertigte, zusammengesetzte Komponenten übertragen.
Leider erwies sich diese Methode auch als unflexibel. Deshalb:
- Beide Requisiten sind erforderlich. Dies schränkt die Wiederverwendung von Komponenten ein
- Optional würde das Überladen der SplitPane-Komponente mit Logik bedeuten.
- Verschachtelung und Pluralität werden nicht sehr semantisch dargestellt.
- Diese Zuordnungslogik müsste für jede Komponente, die Requisiten akzeptiert, neu geschrieben werden.
Infolgedessen kann eine solche Lösung selbst für relativ einfache Szenarien immer komplexer werden:
function SplitPane(props) { return ( <div className="SplitPane"> { props.left && <div className="SplitPane-left"> {props.left} </div> } { props.right && <div className="SplitPane-right"> {props.right} </div> } </div> ); } function App() { return ( <SplitPane left={ contacts.map(el => <Contacts name={ <ContactsName name={el.name} /> } phone={ <ContactsPhone name={el.phone} /> } /> ) } right={ <Chat /> } /> ); }
In der Dokumentation wird ein ähnlicher Code für Komponenten höherer Ordnung (HOC) und Render-Requisiten als " Wrapper-Hölle" bezeichnet. Mit jedem Hinzufügen eines neuen Elements wird die Lesbarkeit des Codes schwieriger.
Eine Antwort auf dieses Problem - Slots - gibt es in der Webkomponententechnologie und im Vue-Framework . An beiden Stellen gibt es jedoch Einschränkungen: Erstens werden die Slots nicht durch ein Symbol, sondern durch eine Zeichenfolge definiert, was das Refactoring erschwert. Zweitens sind Steckplätze in ihrer Funktionalität eingeschränkt und können nicht ihre eigene Anzeige steuern, andere Steckplätze auf untergeordnete Komponenten übertragen oder in anderen Elementen wiederverwendet werden.
Kurz gesagt, nennen wir es Muster Nr. 5 - Slots :
function App() { return ( <SplitPane> <LeftPane> <Contacts /> </LeftPane> <RightPane> <Chat /> </RightPane> </SplitPane> ); }
Darüber werde ich im nächsten Artikel über vorhandene Lösungen für das Slot-Muster in React und über meine eigene Lösung sprechen.