Vereinheitlichung visueller Komponenten. Teil 1. Stile



Dieser Artikel ist in erster Linie für Entwickler hilfreich, die nicht mit vorgefertigten Komponentensätzen wie material-ui arbeiten, sondern ihre eigenen implementieren. Zum Beispiel wurde ein Design für ein Produkt entwickelt, das widerspiegelt, wie Schaltflächen, modale Fenster usw. aussehen sollen. Um ein solches Entwurfssystem korrekt zu implementieren, müssen alle seine Atome eine gute Unterstützung für ihre Zusammensetzung bieten. Mit anderen Worten, es muss sichergestellt werden, dass sich jedes einzelne Bauteil perfekt in ein größeres Verbundbauteil integrieren lässt. Und wenn er nicht passt, wäre es schön, eine einfache Unterstützung für seine Anpassung zu haben. Wie dem auch sei, dies ist ein eigenständiges großes Thema, und ich werde vielleicht ein anderes Mal darauf zurückkommen.

Texte


Hallo allerseits. Ich beginne meine Reise am Hub mit einem einfachen, aber nützlichen Artikel. Was mich betrifft, hat es sich als zu detailliert herausgestellt, aber ich habe trotzdem versucht, mich an den Leser und nicht an mich selbst anzupassen. Bevor ich den nächsten Artikel (falls vorhanden) zu einem komplexeren Thema schreibe, möchte ich meine Präsentation auf das Feedback (falls vorhanden) abstimmen.

Verwendete Begriffe:

  • Eine visuelle Komponente ist eine Komponente, die ein im DOM eingebettetes Element zurückgibt. Zum Beispiel return (<div />) . Eine Komponente, die nur eine andere Komponente zurückgibt, sollte nicht visuell interpretiert werden.

Einleitung


Wenn Sie eine Komponente entwickeln, können Sie sie nicht vollständig universell machen. In Ihrem Kopf gehen Sie immer von bestimmten Verwendungsmöglichkeiten aus. Es stellt sich oft heraus, dass Ihre Kollegen nach der Entwicklung anfangen, "diese Komponente überall hin zu schieben". Sie sind sauer auf sie: „Nun, ich habe es dafür nicht entwickelt! Es ist nicht für diese Aufgabe gedacht! “ Natürlich sind Verbesserungen unvermeidlich und sogar notwendig. Dies sollte jedoch keine Verbesserung sein, wie das Werfen neuer Requisiten, um den Einzug von 4 auf 8 Pixel zu erhöhen, der in ein oder zwei von fünfzig Fällen verwendet wird. Komponenten müssen eine benutzerdefinierte externe Geometrie haben.

TypeScript, helfen Sie aus


Betrachten Sie die Schnittstelle, die sich in ihrer Bedeutung beispielsweise in src/Library/Controls.ts . Es werden kurze Kommentare zu den Feldern gegeben. Im Folgenden werden sie genauer analysiert.

 export interface VisualComponentProps { //  .   - children?: React.ReactNode; //    css className?: string; //   ,     . doNotRender?: boolean; //     doNotRender true fallback?: JSX.Element; //    css style?: React.CSSProperties; } 

Dies ist die Komponenten-Requisiten-Schnittstelle. Welche? Alle visuellen Komponenten. Sie sollten auf das Stammelement angewendet werden.

Die Requisiten jeder entwickelten visuellen Komponente sollten über diese Schnittstelle erweitert werden.

Bitte beachten Sie ab sofort, dass alle diese Requisiten optional sind. Betrachten Sie sie.

  • children befinden sich in React.Component-Klassenkomponenten, React.FC-Komponentenkomponenten, aber sie befinden sich nicht in normalen Funktionen, ohne die React.FC-Typisierung anzugeben. Deshalb fragen wir ihn.
  • className/style ähnliche Namen wie im üblichen JSX'nom <div />. Wir produzieren keine Semantik. Dieses Prinzip der Identität des Namens wird beispielsweise in Requisiten verwendet, um den Verweis-Link anzugeben .
  • doNotRender als Alternative zur schmerzhaften Krücke beim JSX- Rendering nach Bedingungen verwendet . Bei Anwendung dieser Lösung müssen die Rendermethoden nicht in geschweifte Klammern gesetzt werden, was die Lesbarkeit des Codes beeinträchtigt. Vergleichen Sie 2 Code-Schnipsel:

    Virgin bedingte Wiedergabe:

    App.tsx

     renderComponent() { const {props, state} = this; const needRender = state.something; return ( <PageLayout> <UIButton children={'This is a button'} /> {needRender && <UIButton children={'This is another button'} /> } </PageLayout> ); } 

    Tschad Requisiten doNotRender:

    App.tsx

     renderComponent() { const {props, state} = this; const needRender = state.something; return ( <PageLayout> <UIButton children={'This is a button'} /> <UIButton children={'This is another button'} doNotRender={!needRender} /> </PageLayout> ); } 

    In der ersten Version erhöhen wir die Verschachtelungsebene der unteren Schaltfläche, obwohl ihre Verschachtelung in Bezug auf die Bedeutung auf derselben Ebene liegt wie die der oberen. In meinem Editor sieht es schlecht aus, wenn ich einen Tabulator mit einer Breite von 2 Leerzeichen verwende, und hier ist es noch schlimmer.

    In der zweiten Option haben wir die gleiche Verschachtelung, abzüglich, dass doNotRender möglicherweise nicht ins Auge fällt und der Entwickler nicht versteht, was passiert. Wenn jedoch in Ihrem Projekt jede visuelle Komponente nach diesem Prinzip erstellt wird, verschwindet dieses Problem sofort.
  • fallback erforderlich, wenn wir mit doNotRender true nicht null rendern doNotRender true , sondern eine Art benutzerdefiniertes Element. Es wird analog zu React Suspense verwendet , da es eine ähnliche Bedeutung hat (wir produzieren keine Semantik).

Ich möchte zeigen, wie man es richtig benutzt. Lassen Sie uns einen einfachen Knopf machen.

Hinweis: Im folgenden Code verwende ich auch CSS-Module, SASS und Klassennamen.

UIButton.tsx

 import * as React from 'react'; import { VisualComponentProps } from 'Library/Controls'; import * as css from './Button.sass'; import cn from 'classnames'; //  ()  export interface ButtonBasicProps { disabled?: boolean; } export interface ButtonProps extends ButtonBasicProps, VisualComponentProps {} export function UIButton(props: ButtonProps) { //   undefined,     // "Nothing was returned from render." if (props.doNotRender) return props.fallback || null; //      const rootClassNames = cn( // ,   sass css.Button, // ,     props props.className, //      props.disabled && css._disabled ); return ( <div children={props.children} className={rootClassNames} style={props.style} /> ) } 

App.tsx

 renderComponent() { const {props, state} = this; const needRenderSecond = true; return ( <PageLayout> <UIButton children={'This is a button'} style={{marginRight: needRenderSecond ? 5 : null}} /> <UIButton disabled children={'This is another button'} doNotRender={!needRenderSecond} /> </PageLayout> ); } 

Ergebnis:



Reflexion und Fazit


Es ist praktisch, mit Komponenten wie divs zu arbeiten und verschiedene Wrapper, Kompositionen und Spezialisierungen zu erstellen, die über den Rahmen der in ihnen eingebetteten ursprünglichen Funktionalität hinausgehen .

Es kann argumentiert werden, dass, da es keine bedingten gelben Schaltflächen im Entwurfssystem gibt und der Entwickler sie erstellen muss, das Problem nicht in den Komponenten liegt, sondern in der Tatsache, dass diese benötigt werden. In der Realität treten solche Situationen jedoch häufig auf. "... aber wir müssen leben! Wir müssen leben." Darüber hinaus kann das CSS-Kaskadenprinzip in der Praxis nicht immer implementiert werden, und es kann vorkommen, dass sich Ihre Stile einfach mit der höheren Spezifität eines anderen Selektors (oder der oben beschriebenen) überschneiden. Hier hilft nur Stil.

Abschließend möchte ich einige (buchstäblich) Momente hinzufügen.

  1. Beachten Sie, dass doNotRender das Verhalten beim bedingten Rendern nicht vollständig wiederholt. Sie werden auch Lebenszyklusmethoden ausführen. Beim Rendern wird nur ein Fallback oder Null zurückgegeben. In einigen komplexen Komponenten möchten Sie möglicherweise die Ausführung von Lebenszyklusmethoden vermeiden. Dazu müssen Sie lediglich eine Spezialisierung Ihrer Komponente vornehmen.

    Verwenden des UIButton-Beispiels: Benennen Sie den UIButton in UIButtonInner um, und fügen Sie den folgenden Code hinzu:

    UIButton.tsx

     export function UIButton(props: ButtonProps) { if (props.doNotRender) return props.fallback || null; return <UIButtonInner {...props} />; } 

    PS Machen Sie in dieser Funktion keinen rekursiven UIButton-Aufruffehler!
  2. In seltenen Fällen, in denen Stile auf dem Wrapper und auf der umschlossenen Komponente unabhängig voneinander geändert werden können, ist die folgende Benutzeroberfläche möglicherweise hilfreich für Sie

    Library/Controls.ts

     export interface VisualComponentWrapperProps extends VisualComponentProps { wrappedVisual?: VisualComponentProps; } 

    Und seine Verwendung
    UIButton.tsx

     interface ButtonSomeWrapperProps extends ButtonBasicProps, VisualComponentWrapperProps { myCustomProp?: number; } export function UIButtonSomeWrapper(props: ButtonSomeWrapperProps) { if (props.doNotRender) return props.fallback || null; const { //  VisualComponentProps  style, className, children, fallback, doNotRender, // VisualComponentProps   wrappedVisual, //    myCustomProp, //     ...uiButtonProps } = props; return ( <div style={style} className={className} > {myCustomProp} <UIButton {...wrappedVisual} {...uiButtonProps} /> {children} </div> ); } 

Durch die Entwicklung einer Anwendung mit diesem Ansatz wird die Wiederverwendbarkeit Ihrer Komponenten erheblich verbessert, die Anzahl unnötiger Krückenstile (die in den Stildateien der Komponente ausschließlich für die Anforderungen anderer Komponenten beschriebenen Stile) und Requisiten verringert und strukturierter Code hinzugefügt. Das ist alles Im nächsten Artikel werden wir beginnen, die Probleme der Wiederverwendbarkeit von Komponenten mehr in Bezug auf Code als in Bezug auf CSS zu lösen. Oder ich schreibe über etwas Interessanteres. Vielen Dank für Ihre Aufmerksamkeit.

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


All Articles