Construire un système de conception réutilisable avec React

React a beaucoup fait pour simplifier le développement Web. L'architecture à base de composants de React facilite en principe la décomposition et la réutilisation du code. Cependant, les développeurs ne savent pas toujours comment partager leurs composants entre projets. Dans ce post, je vais vous montrer quelques moyens de résoudre ce problème..

React a facilité l'écriture de beaux codes expressifs. Cependant, sans modèles clairs pour la réutilisation des composants, le code devient divergent avec le temps et devient très difficile à maintenir. J'ai vu des bases de code où le même élément d'interface utilisateur avait dix implémentations différentes! Un autre problème est que, plus souvent qu'autrement, les développeurs ont tendance à coupler trop étroitement l'interface utilisateur et les fonctionnalités métier et à lutter plus tard lorsque l'interface change.

Aujourd'hui, nous verrons comment créer des composants d'interface utilisateur partageables et comment établir un langage de conception cohérent pour l'ensemble de votre application..

Commencer

Vous avez besoin d'un projet React vide pour commencer. Le moyen le plus rapide de le faire est de créer-réagir-application, mais cela nécessite quelques efforts pour configurer Sass avec cela. J'ai créé une application squelette que vous pouvez cloner à partir de GitHub. Vous pouvez également trouver le projet final dans notre tutoriel GitHub repo.

Pour courir, fais un fil-installer pour extraire toutes les dépendances, puis exécuter début de fil pour faire apparaître l'application. 

Tous les composants visuels résideront sous la système de conception dossier avec les styles correspondants. Tous les styles globaux ou variables seront sous src / styles.

Configuration de la base de conception

À quand remonte la dernière fois où vous avez un regard que vous êtes mort-à-moi de la part de vos collègues concepteurs, qui s'est trompé en ajoutant un remplissage d'un demi-pixel ou en ne pouvant pas différencier les différentes nuances de gris? (Il y a une différence entre #eee et #efefef, On me dit et j’ai l’intention de le découvrir un de ces jours.)

L'un des objectifs de la création d'une bibliothèque d'interface utilisateur est d'améliorer la relation entre l'équipe de conception et de développement. Les développeurs front-end se coordonnent depuis un certain temps déjà avec les concepteurs d'API et savent bien établir des contrats d'API. Mais pour une raison quelconque, cela nous échappe lors de la coordination avec l'équipe de conception. Si vous y réfléchissez, il y a seulement un nombre fini d'états dans lequel un élément d'interface utilisateur peut exister. Si nous concevons un composant Heading, par exemple, il peut être compris entre h1 et h6 et peut être gras, en italique ou souligné. Il devrait être simple de codifier cette.

Le système de grille

Avant de se lancer dans un projet de conception, la première étape consiste à comprendre la structure des grilles. Pour de nombreuses applications, c'est simplement aléatoire. Cela conduit à un système d'espacement dispersé et rend très difficile pour les développeurs de déterminer quel système d'espacement utiliser. Alors, choisissez un système! Je suis tombé en amour avec le système de grille 4px - 8px quand j'ai lu pour la première fois à ce sujet. S'en tenir à cela a aidé à simplifier beaucoup de problèmes de style.

Commençons par mettre en place un système de grille de base dans le code. Commençons par un composant de l'application qui définit la mise en page..

//src/App.js import React, Component de 'react'; importer le logo depuis './logo.svg'; import './App.scss'; importer Flex, Page, Box, BoxStyle de './design_system/layouts/Layouts'; La classe App étend le composant render () return ( 
logo

Construire un système de conception avec React

Une simple boite souple Milieu et cela va à droite
) export par défaut App;

Ensuite, nous définissons un certain nombre de styles et de composants de wrapper.

//design-system/layouts/Layout.js import Réagissez à partir de 'react'; import './layout.scss'; export const BoxBorderStyle = défaut: 'ds-box-border - défaut', clair: 'ds-box-border - léger', épais: 'ds-box-border - épais', export const BoxStyle =  default: 'ds-box - default', doubleSpace: 'ds-box - double-space', noSpace: 'ds-box - no-space' export const Page = (enfants, fullWidth = true) => const classNames = 'ds-page $ fullWidth? 'ds-page - fullwidth': " '; return (
enfants
) ; export const Flex = (enfants, lastElRight) => const classNames = 'flex $ lastElRight?? 'flex-align-right': ""; return (
enfants
) ; exportation const Box = (enfants, borderStyle = BoxBorderStyle.default, boxStyle = BoxStyle.default, fullWidth = true) => const classNames = 'ds-box $ borderStyle $ boxStyle $ fullWidth? 'ds-box - fullwidth': " '; return (
enfants
) ;

Enfin, nous définirons nos styles CSS dans SCSS.

/*design-system/layouts/layout.scss * / @import '… /… /styles/variables.scss'; $ base-padding: $ base-px * 2; .flex display: flex; & .flex-align-right> div: dernier-enfant marge-gauche: auto;  .ds-page border: 0px solid # 333; border-left-width: 1px; border-right-width: 1px; &: not (.ds-page - fullwidth) margin: 0 auto; largeur maximale: 960px;  & .ds-page - fullwidth max-width: 100%; marge: 0 $ base-px * 10;  .ds-box border-color: # f9f9f9; style de bordure: solide; text-align: left; & .ds-box - pleine largeur width: 100%;  & .ds-box-border - light border: 1px;  & .ds-box-border - épais border-width: $ base-px;  & .ds-box - default padding: $ base-padding;  & .ds-box - double espace padding: $ base-padding * 2;  & .ds-box - default - no-space padding: 0; 

Il y a beaucoup à décompresser ici. Commençons par le bas. variables.scss C’est là que nous définissons nos globals comme des couleurs et que nous configurons la grille. Puisque nous utilisons la grille 4px-8px, notre base sera 4px. Le composant parent est Page, et cela contrôle le flux de la page. Alors l'élément de niveau le plus bas est un Boîte, qui détermine comment le contenu est rendu dans une page. C'est juste un div qui sait se rendre contextuellement. 

Maintenant, nous avons besoin d'un Récipient composant qui colle ensemble plusieurs divs. Nous avons choisi flex-box, d'où le nom créatif Fléchir composant. 

Définir un système de types

Le système de types est un composant essentiel de toute application. Habituellement, nous définissons une base par le biais de styles globaux et les remplaçons au besoin. Cela conduit souvent à des incohérences dans la conception. Voyons comment cela peut être facilement résolu en ajoutant à la bibliothèque de conception.

Tout d'abord, nous allons définir des constantes de style et une classe wrapper.

// design-system / type / type.js importer Réagir, Composant à partir de 'réagir'; import './type.scss'; export const TextSize = par défaut: 'ds-text-size - par défaut', sm: 'ds-text-size - sm', lg: 'ds-text-size - lg'; export const TextBold = default: 'ds-text - default', semibold: 'ds-text - semibold', gras: 'ds-text - bold'; export const Type = (tag = 'span', size = TextSize.default, boldness = TextBold.default, enfants) => const Tag = '$ tag'; const classNames = 'ds-text $ taille $ gras'; revenir  enfants  ;

Ensuite, nous allons définir les styles CSS qui seront utilisés pour les éléments de texte.

/ * design-system / type / type.scss * / @import '… /… /styles/variables.scss'; $ base-font: $ base-px * 4; .ds-text line-height: 1.8em; & .ds-text-size - default font-size: $ base-font;  & .ds-text-size - sm taille de la police: $ base-font - $ base-px;  & .ds-text-size - lg taille de la police: $ base-font + $ base-px;  & strong, & .ds-text - semibold font-weight: 600;  & .ds-text - bold font-weight: 700; 

C'est simple Texte Un composant représentant les différents états de l'interface utilisateur peut contenir du texte. Nous pouvons l'étendre davantage afin de gérer des micro-interactions telles que le rendu des info-bulles lorsque le texte est tronqué, ou le rendu d'un nugget différent pour des cas particuliers tels que le courrier électronique, l'heure, etc.. 

Atomes forment des molécules

Jusqu'à présent, nous n'avons construit que les éléments les plus élémentaires pouvant exister dans une application Web, et ils ne sont d'aucune utilité. Développons cet exemple en construisant une simple fenêtre modale. 

Tout d'abord, nous définissons la classe de composant pour la fenêtre modale.

// design-system / Portal.js import Réagir, Composant depuis 'réagir'; importer ReactDOM de 'react-dom'; importer Box, Flex de './layouts/Layouts'; importer Type, TextSize, TextAlign depuis './type/Type'; import './portal.scss'; La classe d'exportation Portal s'étend React.Component constructeur (props) super (props); this.el = document.createElement ('div');  composantDidMount () this.props.root.appendChild (this.el);  composantWillUnmount () this.props.root.removeChild (this.el);  render () return ReactDOM.createPortal (this.props.children, this.el,);  export const Modal = (enfants, root, closeModal, en-tête) => retour  
entête X enfants

Ensuite, nous pouvons définir les styles CSS pour le modal.

# modal-root . modal-wrapper background-color: white; border-radius: 10px; hauteur maximale: calc (100% - 100px); largeur maximale: 560px; largeur: 100%; en haut: 35%; à gauche: 35%; à droite: auto; en bas: auto; z-index: 990; position: absolue; > div background-color: transparentize (noir, .5); position: absolue; z-index: 980; en haut: 0; à droite: 0; gauche: 0; en bas: 0;  .close curseur: pointeur; 

Pour les non-initiés, createPortal est très similaire à la rendre méthode, sauf qu’il restitue les enfants dans un noeud qui existe en dehors de la hiérarchie DOM du composant parent. Il a été introduit dans React 16.

Utilisation du composant modal

Maintenant que le composant est défini, voyons comment nous pouvons l'utiliser dans un contexte métier.

//src/App.js import React, Component de 'react'; //… import Type, TextBold, TextSize from './design_system/type/Type'; importer Modal de './design_system/Portal'; la classe App étend Component constructor () super (); this.state = showModal: false toggleModal () this.setState (showModal:! this.state.showModal);  render () //…  this.state.showModal &&  Test de rendu  //… 

Nous pouvons utiliser le modal n'importe où et conserver l'état dans l'appelant. Simple, non? Mais il y a un bug ici. Le bouton de fermeture ne fonctionne pas. C'est parce que nous avons construit tous les composants en tant que système fermé. Il ne consomme que les accessoires dont il a besoin et ne tient pas compte du reste. Dans ce contexte, le composant texte ignore les sur clic gestionnaire d'événements. Heureusement, c'est une solution facile. 

// Dans design-system / type / Type.js export const Type = (tag = 'span', size = TextSize.default, boldness = TextBold.default, enfants, className = ", align = TextAlign.default,… reste ) => const Tag = '$ tag'; const classNames = 'ds-text $ taille $ gras $ align $ className'; return  enfants  ; 

ES6 dispose d’un moyen pratique d’extraire les paramètres restants sous forme de tableau. Il suffit d'appliquer cela et de les répartir sur le composant.

Rendre les composants découvrables

Au fur et à mesure que votre équipe grandit, il est difficile de mettre tout le monde au courant des composants disponibles. Les livres de contes sont un excellent moyen de rendre vos composants découvrables. Mettons en place un composant de base de livre de contes. 

Pour commencer, lancez:

npm i -g @ storybook / cli getstorybook

Ceci configure la configuration requise pour le livre de contes. À partir de là, le reste de la configuration est un jeu d'enfant. Ajoutons une histoire simple pour représenter différents états de Type

importer Réagir de 'réagir'; importer storiesOf de '@ storybook / react'; importer Type, TextSize, TextBold depuis '… /design_system/type/Type.js'; storiesOf ('Type', module) .add ('Texte par défaut', () => (  Lorem ipsum  )). add ('texte en gras', () => (  Lorem ipsum  )). add ('texte d'en-tête', () => (  Lorem ipsum  )); 

La surface de l'API est simple. histoires de définit une nouvelle histoire, généralement votre composant. Vous pouvez ensuite créer un nouveau chapitre avec ajouter, mettre en valeur les différents états de ce composant. 

Bien sûr, ceci est assez basique, mais les livres de contes comportent plusieurs add-ons qui vous aideront à ajouter des fonctionnalités à vos documents. Et ai-je mentionné qu'ils ont un support emoji? 😲

Intégration à une bibliothèque de conceptions prête à l'emploi

Concevoir un système de conception à partir de zéro représente beaucoup de travail et peut ne pas avoir de sens pour une application plus petite. Mais si votre produit est riche et que vous avez besoin de beaucoup de flexibilité et de contrôle sur ce que vous construisez, la création de votre propre bibliothèque d'interface utilisateur vous aidera à plus long terme.. 

Je n'ai pas encore vu de bonne bibliothèque de composants d'interface utilisateur pour React. Mon expérience avec react-bootstrap et material-ui (la bibliothèque de React, c’est-à-dire pas le framework lui-même) n’était pas géniale. Au lieu de réutiliser une bibliothèque d'interface utilisateur complète, il peut être judicieux de sélectionner des composants individuels. Par exemple, la mise en œuvre de la sélection multiple est un problème complexe d’interface utilisateur et de nombreux scénarios doivent être envisagés. Dans ce cas, il pourrait être plus simple d’utiliser une bibliothèque comme React Select ou Select2.

Un mot de prudence, cependant. Toutes les dépendances externes, en particulier les plugins d'interface utilisateur, constituent un risque. Ils sont tenus de changer souvent leurs API ou, à l’inverse, de continuer à utiliser des fonctionnalités obsolètes et obsolètes de React. Cela peut avoir une incidence sur votre prestation technique et tout changement peut être coûteux. Je conseillerais d'utiliser un wrapper sur ces bibliothèques, il sera donc facile de remplacer la bibliothèque sans toucher plusieurs parties de l'application.

Conclusion

Dans cet article, je vous ai montré comment diviser votre application en éléments visuels atomiques, en les utilisant comme des blocs Lego pour obtenir l'effet souhaité. Cela facilite la réutilisation et la maintenabilité du code, tout en facilitant la maintenance d'une interface utilisateur cohérente dans l'ensemble de votre application..

Partagez vos opinions sur cet article dans la section commentaires!