Construiți un sistem de design reutilizabil cu React

React a făcut multe pentru a simplifica dezvoltarea web. Reacția arhitecturii bazate pe componente ușurează, în principiu, descompunerea și reutilizarea codului. Cu toate acestea, nu este întotdeauna clar pentru dezvoltatori cum să împărtășească componentele lor în cadrul proiectelor. În acest post, vă voi arăta câteva modalități de a rezolva asta.

React a făcut mai ușor să scrieți un cod frumos, expresiv. Cu toate acestea, fără modele clare de reutilizare a componentelor, codul devine divergent în timp și devine foarte greu de întreținut. Am văzut baze de coduri în care același element UI avea zece implementări diferite! O altă problemă este că, de cele mai multe ori, dezvoltatorii tind să cupleze UI și funcționalitatea afacerii prea strâns și să lupte mai târziu atunci când UI se schimbă.

Astăzi, vom vedea cum putem crea componente de interfață distribuită și cum să stabilim un limbaj de design consecvent în întreaga aplicație.

Noțiuni de bază

Aveți nevoie de un proiect React gol pentru a începe. Cel mai rapid mod de a face acest lucru este prin intermediul aplicației create-react-app, dar este nevoie de un efort pentru a înființa Sass cu acest lucru. Am creat o aplicație de schelet, pe care o puteți clona de la GitHub. De asemenea, puteți găsi proiectul final în GitHub repo.

Pentru a rula, faceți a fire-instalare pentru a trage toate dependențele și apoi a alerga fire start pentru a aduce cererea. 

Toate componentele vizuale se vor afla sub design_system împreună cu stilurile corespunzătoare. Toate stilurile sau variabilele globale vor fi sub src / stiluri.

Configurarea liniei de bază a proiectului

Când ai fost ultima oară când te-ai uitat la mine de la colegii tăi de proiectare, pentru că ai obstrucționat greutatea cu jumătate de pixel sau nu ai putut să diferențiezi diferite nuanțe de gri? (Există o diferență între #eee și #efefef, Mi sa spus, și intenționez să aflu una din aceste zile.)

Unul dintre scopurile construirii unei biblioteci UI este îmbunătățirea relației dintre echipa de proiectare și dezvoltare. Dezvoltatorii din Front-End au fost coordonatori cu designeri API pentru un timp și sunt buni la stabilirea contractelor API. Dar, dintr-un anumit motiv, ne-a evitat în timp ce coordonăm cu echipa de design. Dacă vă gândiți la asta, există doar un număr finit de state în care poate exista un element UI. Dacă vrem să proiectăm o componentă de rutare, de exemplu, ea poate fi ceva între h1 și h6 și poate fi îndrăzneț, italicizat sau subliniat. Ar trebui să fie ușor să codificați acest lucru.

Sistemul de rețea

Primul pas înainte de a începe orice proiect de proiectare este de a înțelege modul în care sunt structurate grilele. Pentru multe aplicații, este doar aleatoriu. Acest lucru duce la un sistem de spațiere împrăștiat și face foarte greu pentru dezvoltatori să evalueze care sistem de spațiere să folosească. Deci alege un sistem! M-am îndrăgostit de sistemul de grilă 4px - 8px când am citit prima dată despre el. Lipirea acestui lucru a ajutat la simplificarea numeroaselor aspecte ale stilului.

Să începem prin configurarea unui sistem de bază în rețea. Vom începe cu o componentă a aplicației care stabilește aspectul.

//src/App.js import Reacționează, Component din "reacționează"; sigla de import din "./logo.svg"; import "./App.scss"; importă Flex, Pagina, Box, BoxStyle din "./design_system/layouts/Layouts"; clasa App extinde Component render () return ( 
siglă

Construiți un sistem de proiectare cu React

Un flexbox simplu Mijloc și asta merge spre dreapta
); exportă aplicația implicită;

Apoi, definim un număr de stiluri și componente de înfășurare.

//design-system/layouts/Layout.js import Reaction from 'react'; import "./layout.scss '; export const BoxBorderStyle = implicit: 'ds-box-border-default', light: 'ds-box-border- light', grosime: implicit: 'ds-box - implicit', doubleSpace: 'ds-box - spațiu dublu', noSpace: 'ds-box - no-space' export const pagina = children, fullWidth = true) => const clasăName = 'ds-page $ fullWidth? 'ds-page - fullwidth': " '; retur (
copii
); ; export const Flex = (copii, lastElRight) => const classNames = 'flex $ lastElRight? 'flex-align-right': ''; retur (
copii
); ; export const Box = (copii, borderStyle = BoxBorderStyle.default, boxStyle = BoxStyle.default, fullWidth = true) => const classNames = 'ds-box $ borderStyle $ boxStyle $ fullWidth? 'ds-box - fullwidth': " '; retur (
copii
); ;

În cele din urmă, vom defini stilurile noastre CSS în SCSS.

/*design-system/layouts/layout.scss * / @import '... / ... /styles/variables.scss'; $ base-padding: $ base-px * 2; .flex display: flex; & .flex-aliniere-dreapta> div: ultimul-copil margin-stânga: auto; . -page border: 0px solid # 333; frontieră-stânga-lățime: 1px; frontieră-dreapta-lățime: 1px; &: nu (.ds-page - fullwidth) margine: 0 auto; max-lățime: 960px;  & .ds-page - fullwidth max-lățime: 100%; marja: 0 $ base-px * 10;  .box-box border-color: # f9f9f9; stilul frontal: solid; text-aliniere: stânga; & .ds-box - fullwidth lățime: 100%;  & .ds-box-border - lumină border: 1px;  & .ds-box-border - grosime border-width: $ base-px;  & .ds-box - implicit padding: $ base-padding;  & .ds-box - spațiu dublu umplutură: $ base-padding * 2;  & .ds-box - implicit - fără spațiu padding: 0; 

Există multe de despachetat aici. Să începem de jos. variables.scss este locul în care definim culorile noastre ca culoarea și înființăm grila. Deoarece utilizăm grila 4px-8px, baza noastră va fi de 4px. Componenta părinte este Pagină, iar acest lucru controlează fluxul paginii. Atunci elementul cu cel mai mic nivel este a Cutie, care determină modul în care este redat conținutul într-o pagină. E doar un lucru div care știe să se facă în mod contextual. 

Acum, avem nevoie de a recipient componenta care lipeste impreuna multiple divs. Am ales flex-box, prin urmare, numele creativ Contracta component. 

Definirea unui sistem de tip

Sistemul de tip este o componentă critică a oricărei aplicații. De obicei, definim o bază prin stilurile globale și suprascrieți ca și când este necesar. Acest lucru duce deseori la inconsecvențe în design. Să vedem cum poate fi ușor rezolvată această problemă prin adăugarea la biblioteca de proiectare.

Mai întâi vom defini câteva constante de stil și o clasă de înfășurare.

// design-system / type / Type.js Import React, Component de la 'react'; import "./type.scss"; exportare const TextSize = default: 'ds-text-size - implicit', sm: 'ds-text-size - sm', lg: 'ds-text-size - lg'; export const TextBold = implicit: 'ds-text - implicit', semibold: 'ds-text - semibold', bold: 'ds-text - bold'; export const Tip = (tag = 'span', size = TextSize.default, boldness = TextBold.default, copii) => const Tag = '$ tag'; const classNames = 'ds-text $ size $ boldness'; întoarcere  copii  ;

Apoi, vom defini stilurile CSS care vor fi folosite pentru elementele de text.

/ * proiectare-sistem / tip / tip.scss * / @import '... / ... /styles/variables.scss'; $ font-bază: $ base-px * 4; .ds-text linie-înălțime: 1,8; & .ds-text-size - implicit font-size: $ base-font;  & .ds-text-size - sm font-size: $ font-base - $ base-px;  & .ds-text-size - lg font-size: $ font-base + $ base-px;  & strong, și .ds-text - semibold font-weight: 600;  & .ds-text - caractere aldine font-weight: 700; 

Acest lucru este simplu Text componenta reprezentând textele diferitelor stări ale UI poate fi inclusă. Putem extinde acest lucru în continuare pentru a trata micro-interacțiunile, cum ar fi redarea tooltips atunci când textul este tăiat, sau redarea unui nugget diferit pentru cazuri speciale, cum ar fi e-mail, timp, etc. 

Atomi formează molecule

Până acum, am construit doar cele mai elementare elemente care pot exista într-o aplicație web și nu au nici un folos pe cont propriu. Să ne extindem pe acest exemplu construind o fereastră modală simplă. 

Mai întâi, definim clasa componente pentru fereastra modală.

// design-system / Portal.js import Reacționează, Component din "reacționează"; import ReactDOM de la "react-dom"; import Box, Flex din "./layouts/Layouts"; import Type, TextSize, TextAlign de la "./type/Type"; import "./portal.scss"; portul de clasă de export extinde React.Component constructor (recuzită) super (recuzită); this.el = document.createElement ("div");  componentDidMount () this.props.root.appendChild (acest.el);  componentWillUnmount () this.props.root.removeChild (acest.el);  render () returnați ReactDOM.createPortal (this.props.children, this.el,);  export const Modal = (copii, root, closeModal, antet) => retur  
antet X copii

Apoi, putem defini stilurile CSS pentru modal.

# modal-root .modal-wrapper fundal-culoare: alb; raza de graniță: 10 pixeli; înălțime maximă: calc (100% - 100px); max-lățime: 560px; lățime: 100%; top: 35%; stânga: 35%; dreapta: auto; fund: auto; z-index: 990; poziția: absolută; > div fundal-culoare: transparentize (negru, .5); poziția: absolută; z-index: 980; top: 0; dreapta: 0; stânga: 0; fund: 0;  .close cursor: pointer; 

Pentru cei neinițiați, createPortal este foarte asemănător cu face , cu excepția faptului că face copiii într-un nod care există în afara ierarhiei DOM a componentei părinte. A fost introdus în React 16.

Utilizarea componentei modale

Acum, când componenta este definită, să vedem cum putem să o folosim într-un context de afaceri.

//src/App.js import Reacționează, Component din "reacționează"; // ... import Type, TextBold, TextSize din "./design_system/type/Type"; import Modal din "./design_system/Portal"; aplicația de clasă extinde Component constructor () super (); this.state = showModal: false comutareModal () this.setState (showModal:! this.state.showModal);  render () // ...  this.state.showModal &&  Testarea testelor  ... 

Putem folosi modalul oriunde și să menținem starea în apelant. Simplu, nu? Dar există un bug aici. Butonul de închidere nu funcționează. Asta pentru că am construit toate componentele ca un sistem închis. Pur și simplu consumă recuzita de care are nevoie și nu ia în considerare restul. În acest context, componenta text ignoră onClick organizatorul evenimentului. Din fericire, acesta este un remediu ușor. 

// în design-system / type / Type.js export const Tip = (tag = 'span', size = TextSize.default, boldness = TextBold.default, copii, className = ", align = TextAlign.default,  => const Tag = '$ tag'; const classNames = 'ds-text $ size $ class  copii  ; 

ES6 are o modalitate de a extrage parametrii rămași ca o matrice. Doar aplicați acest lucru și împrăștiați-l pe component.

Realizarea componentelor descoperite

Pe măsură ce echipa dvs. scade, este greu să obțineți sincronizarea tuturor cu privire la componentele disponibile. Povestirile sunt o modalitate foarte bună de a face componentele dvs. descoperite. Să formăm o componentă de bază pentru povestiri. 

Pentru a începe, rulați:

npm i-g @ storybook / cli getstorybook

Aceasta stabilește configurația necesară pentru cartea de povești. De aici, este un cinch pentru a face restul de configurare. Să adăugăm o poveste simplă pentru a reprezenta diferite state Tip

import Reacționează de la "reacționează"; import storiesOf de la '@ storybook / react'; import Type, TextSize, TextBold de la "... /design_system/type/Type.js"; storiesOf ('Tip', modul) .add ('text implicit', () => (  Lorem ipsum  )) adăugați ("text bold", () => (  Lorem ipsum  )) adăugați ("text antet", () => (  Lorem ipsum  )); 

Suprafața API este simplă. storiesOf definește o poveste nouă, de obicei componenta dvs. Apoi puteți crea un nou capitol cu adăuga, pentru a prezenta diferitele stări ale acestei componente. 

Desigur, acest lucru este destul de fundamental, dar povestile au mai multe programe de completare care vă vor ajuta să adăugați funcționalități documentelor dvs. Și am menționat că au suport pentru emoji? 😲

Integrarea cu o bibliotecă de proiectare în afara rafturii

Proiectarea unui sistem de design de la zero este o mulțime de lucru și poate să nu aibă sens pentru o aplicație mai mică. Dar dacă produsul dvs. este bogat și aveți nevoie de o mulțime de flexibilitate și control asupra a ceea ce construiți, configurarea propriei biblioteci UI vă va ajuta pe termen lung. 

Am văzut încă o bibliotecă de componente pentru React. Experienta mea cu reactie-bootstrap si material-ui (biblioteca pentru React, adica nu cadrul in sine) nu a fost minunata. În loc să reutilizeze o întreagă bibliotecă UI, alegerea componentelor individuale ar putea avea sens. De exemplu, implementarea multi-select este o problemă complexă a UI și există multe scenarii de luat în considerare. În acest caz, ar putea fi mai simplu să folosiți o bibliotecă ca React Select sau Select2.

Un cuvânt de prudență, totuși. Orice dependență externă, în special pluginurile UI, reprezintă un risc. Ei sunt obligați să-și schimbe API-urile adesea sau, la cealaltă extremă, să continue să utilizeze trăsături vechi, depreciate ale lui React. Acest lucru poate afecta livrarea dvs. tehnică și orice modificare ar putea fi costisitoare. Aș sfătui să folosiți un înveliș peste aceste biblioteci, astfel încât să fie ușor să înlocuiți biblioteca fără a atinge mai multe părți ale aplicației.

Concluzie

În acest post, v-am arătat câteva modalități de a vă împărți aplicația în elemente vizuale atomice, folosind ca niște blocuri Lego pentru a obține efectul dorit. Acest lucru facilitează reutilizarea și întreținerea codului, precum și ușurarea menținerii unui interfață de utilizare consistentă în întreaga aplicație.

Împărtășiți-vă gândurile pe acest articol în secțiunea de comentarii!

Cod