Unification des composants visuels. Partie 1. Styles



Cet article sera principalement utile aux développeurs qui ne travaillent pas avec des ensembles de composants prêts à l'emploi, tels que material-ui, mais implémentent les leurs. Par exemple, un design a été développé pour un produit qui reflète à quoi devraient ressembler les boutons, les fenêtres modales, etc. Pour implémenter correctement un tel système de conception, tous ses atomes devront ajouter un bon support pour leur composition. En d'autres termes, il est nécessaire de s'assurer que tout composant unique peut s'intégrer et s'intégrer parfaitement dans un composant composite plus grand. Et s'il ne rentre pas, ce serait bien d'avoir un support simple pour sa personnalisation. Quoi qu'il en soit, il s'agit d'un grand sujet distinct, et j'y reviendrai peut-être une autre fois.

Paroles


Bonjour à tous. Je commence mon voyage sur le hub par un article simple mais utile. Quant à moi, cela s'est avéré trop détaillé, mais j'ai quand même essayé de m'adapter au lecteur, et non à moi-même. Avant d'écrire le prochain article (s'il y en a un) sur un sujet plus complexe, j'ai envie d'ajuster ma présentation sur le feedback (s'il y en a un).

Termes utilisés:

  • Un composant visuel est un composant qui renvoie un élément incorporé dans le DOM. Par exemple, return (<div />) . Un composant qui renvoie uniquement un autre composant ne doit pas être interprété visuellement.

Présentation


Lorsque vous développez un composant, vous ne pouvez pas le rendre totalement universel. Dans votre tête, vous partez toujours d'options spécifiques pour son utilisation. Il s'avère souvent qu'après le développement, vos collègues commencent à "pousser ce composant n'importe où". Vous êtes en colère contre eux: «Eh bien, je ne l'ai pas développé pour ça! Il n'est pas destiné à cette tâche! » Bien entendu, des améliorations sont inévitables, voire nécessaires. Mais cela ne devrait pas être des améliorations comme lancer de nouveaux accessoires pour augmenter le retrait de 4px à 8px, qui seront utilisés dans un ou deux cas sur cinquante. Les composants doivent avoir une géométrie externe personnalisée.

TypeScript, aidez


Considérez l'interface, qui dans le sens devrait être située, par exemple, dans src/Library/Controls.ts . De brefs commentaires sont donnés pour les champs, ci-dessous nous les analyserons plus en détail.

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

Il s'agit de l'interface des accessoires des composants. Lesquels? Tous les composants visuels. Ils doivent être appliqués à son élément racine.

Les accessoires de chaque composant visuel développé doivent être développés à l'aide de cette interface.

Veuillez immédiatement faire attention au fait que tous ces accessoires sont facultatifs. Considérez-les.

  • children sont dans les composants de la classe React.Component, les composants du composant React.FC, mais ils ne sont pas dans des fonctions ordinaires sans spécifier le typage React.FC. Par conséquent, nous lui demandons.
  • className/style utilisons des noms similaires, comme dans le JSX'nom <div /> habituel. Nous ne produisons pas de sémantique. Ce principe d'identité du nom est utilisé, par exemple, dans les accessoires pour spécifier le lien ref .
  • doNotRender utilisé comme une alternative à la béquille douloureuse dans le rendu JSX par condition . En appliquant cette solution, nous n'avons pas besoin de mettre d'accolades dans les méthodes de rendu, ce qui nuit à la lisibilité du code. Comparez 2 extraits de code:

    Rendu conditionnel vierge:

    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> ); } 

    Les accessoires du Tchad 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> ); } 

    Dans la première version, nous augmentons le niveau d'imbrication du bouton du bas, bien qu'en termes de signification, son imbrication est au même niveau que celui du haut. Cela semble mauvais dans mon éditeur, où j'utilise un onglet d'une largeur de 2 espaces, et ici c'est encore pire.

    Dans la deuxième option, nous avons une imbrication égale, moins que doNotRender peut ne pas attirer l'attention et le développeur ne comprendra pas ce qui se passe. Mais, si dans votre projet chaque composant visuel est réalisé selon ce principe, ce problème disparaît immédiatement.
  • fallback nécessaire si nous ne voulons pas rendre null avec doNotRender true , mais une sorte d'élément personnalisé. Il est utilisé par analogie avec React Suspense , car il a une signification similaire (nous ne produisons pas de sémantique)

Je veux montrer comment l'utiliser correctement. Faisons un simple bouton.

Remarque: dans le code ci-dessous, j'utilise également des modules css, sass et classnames.

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> ); } 

Résultat:



Réflexion et conclusion


Il est commode de fonctionner avec des composants tels que les divs, créant divers wrappers, compositions, spécialisations, allant au-delà du cadre de la fonctionnalité d'origine intégrée .

On peut faire valoir que, comme il n'y a pas de boutons jaunes conditionnels dans le système de conception et que le développeur doit les créer, le problème ne réside pas dans les composants, mais dans le fait que ce besoin crée. Cependant, la réalité est que de telles situations se produisent, et assez souvent. "... Mais nous devons vivre! Nous devons vivre." De plus, le principe de la cascade css ne peut pas toujours être mis en pratique, et vous pouvez rencontrer des cas où vos styles chevauchent simplement la spécificité plus élevée d'un autre sélecteur (ou décrit ci-dessus). Ici, le style aide.

Enfin, j'ajouterai quelques (littéralement) moments.

  1. Notez que doNotRender ne répète pas complètement le comportement de rendu conditionnel. Vous exécuterez également des méthodes de cycle de vie, juste le rendu renverra une solution de remplacement ou null. Dans certains composants complexes, vous souhaiterez peut-être éviter d'exécuter des méthodes de cycle de vie. Pour ce faire, il vous suffit de faire une spécialisation intégrée de votre composant.

    En utilisant l'exemple UIButton: renommez le UIButton en UIButtonInner et ajoutez le code suivant en dessous:

    UIButton.tsx

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

    PS Ne faites pas d'erreur d'appel récursif UIButton dans cette fonction!
  2. Dans de rares cas où les styles sur l'encapsuleur et sur le composant encapsulé peuvent changer indépendamment, l'interface suivante peut vous être utile

    Library/Controls.ts

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

    Et son utilisation
    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> ); } 

Le développement d'une application utilisant cette approche augmentera considérablement la réutilisabilité de vos composants, réduira le nombre de styles de béquilles inutiles (en parlant des styles décrits dans les fichiers de style du composant exclusivement pour les besoins des autres composants) et des accessoires, et ajoutera du code structuré. C’est tout. Dans le prochain article, nous allons commencer à résoudre les problèmes de réutilisation des composants plus en termes de code qu'en CSS. Ou je vais écrire sur quelque chose de plus intéressant. Merci de votre attention.

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


All Articles