Bygg et gjenbrukbart designsystem med reaksjon

React har gjort mye for å forenkle webutvikling. Reacts komponentbaserte arkitektur gjør det i prinsippet enkelt å dekomponere og gjenbruke kode. Det er imidlertid ikke alltid klart for utviklere å dele komponentene sine på tvers av prosjekter. I dette innlegget vil jeg vise deg noen måter å fikse det på.

React har gjort det lettere å skrive vakker, uttrykksfull kode. Men uten klare mønstre for komponentgenbruk blir koden divergerende over tid og blir svært vanskelig å vedlikeholde. Jeg har sett kodebaser der det samme brukergrensesnittet hadde ti forskjellige implementeringer! Et annet problem er at utviklere pleier å knytte brukergrensesnittet og forretningsfunksjonaliteten for tett og sliter senere når brukergrensesnittet endres.

I dag ser vi hvordan vi kan opprette delbare brukergrensesnittkomponenter og hvordan du etablerer et konsekvent designspråt i hele applikasjonen din.

Starter

Du trenger et tomt React-prosjekt for å begynne. Den raskeste måten å gjøre dette på er å skape-reagere-app, men det tar litt innsats å sette opp Sass med dette. Jeg har laget en skjelettapp, som du kan klone fra GitHub. Du kan også finne det siste prosjektet i vår veiledning GitHub repo.

For å kjøre, gjør en garn-install å trekke alle avhengigheter i, og deretter løpe garnstart å hente søknaden. 

Alle de visuelle komponentene vil ligge under design_system mappe sammen med tilhørende stiler. Eventuelle globale stiler eller variabler vil være under src / stiler.

Konfigurere designbaseline

Når var den siste gangen du fikk et du-er-døde-til-meg-blikk fra dine designkammerater, for å få polstring feil med en halv pixel, eller ikke å kunne skille mellom forskjellige nyanser av grå? (Det er forskjell mellom #eee og #efefef, Jeg blir fortalt, og jeg har tenkt å finne det ut en av disse dagene.)

Et av målene med å bygge et UI-bibliotek er å forbedre forholdet mellom design og utviklingsteamet. Front-end-utviklere har koordinert med API-designere for en stund nå, og er gode til å etablere API-kontrakter. Men av en eller annen grunn unngår det oss mens vi koordinerer med designteamet. Hvis du tenker på det, er det bare et begrenset antall stater et brukergrensesnittelement kan eksistere i. Hvis vi skal designe en overskriftskomponent, kan det for eksempel være noe mellom h1 og h6 og kan være fet, kursiv eller understreket. Det bør være greit å kodifisere dette.

The Grid System

Det første trinnet før du påbegynner et designprosjekt er å forstå hvordan nettene er strukturert. For mange apps er det bare tilfeldig. Dette fører til et spredt avstandssystem og gjør det svært vanskelig for utviklere å måle hvilket avstandssystem som skal brukes. Så velg et system! Jeg ble forelsket i 4px - 8px grid-systemet når jeg først leste om det. Å holde seg til det har bidratt til å forenkle mange stilingsproblemer.

La oss starte med å sette opp et grunnrutenett i koden. Vi starter med en app-komponent som angir oppsettet.

//src/App.js import React, Component fra 'reagere'; importer logoen fra './logo.svg'; importer './App.scss'; importer Flex, Page, Box, BoxStyle fra './design_system/layouts/Layouts'; klassen App utvider komponent render () return ( 
logo

Bygg et designsystem med React

En enkel flexbox Midten og dette går til høyre
); eksporter standard app;

Deretter definerer vi en rekke stilarter og wrapper-komponenter.

//design-system/layouts/Layout.js import React from 'react'; importer './layout.scss'; eksport const BoxBorderStyle = standard: 'ds-box-border - default', lys: 'ds-box-border-light', tykk: 'ds-box-border-thick', eksport const BoxStyle = standard: 'ds-boks - standard', doubleSpace: 'ds-boks - dobbeltrom', noSpace: 'ds-box - no-space' eksport const Page = (children, fullWidth = true) => const classNames = 'ds-page $ fullWidth? 'ds-side - fullbredde': " '; returnere (
barn
); ; eksport const Flex = (barn, lastElRight) => const classNames = 'flex $ lastElRight? 'flex-align-right': " '; returnere (
barn
); ; eksport const Box = (barn, borderStyle = BoxBorderStyle.default, boxStyle = BoxStyle.default, fullWidth = true) => const classNames = 'ds-box $ borderStyle $ boxStyle $ fullWidth? 'ds-boks - fullbredde': " '; returnere (
barn
); ;

Endelig definerer vi våre CSS-stiler i SCSS.

/*design-system/layouts/layout.scss * / @import '... / ... /styles/variables.scss'; $ base-padding: $ base-px * 2; .flex display: flex; & .flex-align-right> div: siste barn margin-left: auto;  .ds-side border: 0px solid # 333; border-left-width: 1px; border-right-width: 1px; &: ikke (.ds-side - fullbredde) margin: 0 auto; maksimal bredde: 960px;  & .ds-side - fullbredde maksimal bredde: 100%; margin: 0 $ base-px * 10;  .ds-box border-color: # f9f9f9; border-style: solid; tekstjustering: venstre; & .ds-box - fullbredde bredde: 100%;  & .ds-box-border - light border: 1px;  & .ds-box-border - tykk border-width: $ base-px;  & .ds-boks - standard polstring: $ base-padding;  & .ds-boks - dobbeltrom polstring: $ base-padding * 2;  & .ds-boks - standard - ingen plass polstring: 0; 

Det er mye å pakke ut her. La oss starte fra bunnen. variables.scss er der vi definerer våre globals som farge og setter opp nettet. Siden vi bruker 4px-8px-nettverket, blir basen vår 4px. Foreldrekomponenten er Side, og dette styrer strømmen av siden. Da er lavest nivå elementet a Eske, som bestemmer hvordan innhold gjengis på en side. Det er bare en div som vet hvordan man gjengir seg kontekstuelt. 

Nå trenger vi en Container komponent som limes sammen flere divs. Vi har valgt flex-box, dermed den kreativt navngitte Flex komponent. 

Definere et Type System

Typesystemet er en kritisk del av enhver applikasjon. Vanligvis definerer vi en base gjennom globale stiler og overstyrer når og når det er nødvendig. Dette fører ofte til inkonsekvenser i design. La oss se hvordan dette lett kan løses ved å legge til designbiblioteket.

Først skal vi definere noen stilkonstanter og en innpakningsklasse.

// design-system / type / Type.js import React, Component fra 'react'; importer './type.scss'; eksport const TextSize = standard: 'ds-text-size - default', sm: 'ds-tekststørrelse - sm', lg: 'ds-text-size - lg'; eksport const TextBold = standard: 'ds-text - standard', semibold: 'ds-text - semibold', fet: 'ds-text - bold'; eksport const Type = (tag = 'span', size = TextSize.default, boldness = TextBold.default, barn) => const Tag = '$ tag'; const classNames = 'ds-text $ size $ boldness'; komme tilbake   barn  ;

Deretter definerer vi CSS-stilene som skal brukes til tekstelementer.

/ * design-system / type / type.scss * / @import '... / ... /styles/variables.scss'; $ base-font: $ base-px * 4; .ds-tekst linjehøyde: 1.8em; & .ds-text-size - standard font-size: $ base-font;  & .ds-tekststørrelse - sm font-size: $ base-font - $ base-px;  & .ds-tekststørrelse - lg font-size: $ base-font + $ base-px;  & strong, & .ds-text - semibold font-weight: 600;  & .ds-tekst - fet font-weight: 700; 

Dette er en enkel Tekst komponent som representerer ulike UI-stater tekst kan være. Vi kan utvide dette videre for å håndtere mikro-interaksjoner som gjengivelse av verktøytips når teksten er klippet eller gjengitt en annen nugget for spesielle tilfeller som e-post, tid osv.. 

Atomer Form Molecules

Så langt har vi bygget bare de mest grunnleggende elementene som kan eksistere i et webprogram, og de er ikke til bruk på egen hånd. La oss utvide på dette eksempelet ved å bygge et enkelt modalvindu. 

Først definerer vi komponentklassen for det modale vinduet.

// design-system / Portal.js import React, Component fra 'reagere'; importere ReactDOM fra 'react-dom'; importer Box, Flex fra './layouts/Layouts'; importer Type, TextSize, TextAlign fra './type/Type'; importer './portal.scss'; eksport klasse Portal utvider React.Component konstruktør (rekvisitter) super (rekvisitter); this.el = document.createElement ('div');  componentDidMount () this.props.root.appendChild (this.el);  componentWillUnmount () this.props.root.removeChild (this.el);  gjengivelse () returnere ReactDOM.createPortal (this.props.children, this.el,);  eksport const Modal = (barn, rot, closeModal, header) => return  
Overskrift x barn

Deretter kan vi definere CSS-stilene for modal.

# modal-root .modal-wrapper bakgrunnsfarge: hvit; border-radius: 10px; maksimal høyde: calc (100% - 100px); maksimal bredde: 560px; bredde: 100%; topp: 35%; venstre: 35%; høyre: auto; bunn: auto; z-indeks: 990; posisjon: absolutt; > div bakgrunnsfarge: gjennomsiktig (svart, .5); posisjon: absolutt; z-indeks: 980; topp: 0; høyre: 0; venstre: 0; bunn: 0;  .close cursor: pointer; 

For uinitiert, createPortal er veldig lik den gjengi metode, bortsett fra at det gjør barn til en node som eksisterer utenfor DOM-hierarkiet av foreldrekomponenten. Det ble introdusert i React 16.

Bruke Modal Component

Nå som komponenten er definert, la oss se hvordan vi kan bruke den i en forretningskontekst.

//src/App.js import React, Component fra 'reagere'; // ... import Type, TextBold, TextSize fra './design_system/type/Type'; importer Modal fra './design_system/Portal'; klasse App utvider komponent constructor () super (); this.state = showModal: false toggleModal () this.setState (showModal:! this.state.showModal);  gjengivelse () // ...  this.state.showModal &&  Test gjengivelse  // ... 

Vi kan bruke modal hvor som helst og vedlikeholde staten i den som ringer. Enkelt, ikke sant? Men det er en feil her. Lukkeknappen virker ikke. Det er fordi vi har bygget alle komponentene som et lukket system. Det bruker bare rekvisitter det trenger og ignorerer resten. I denne sammenheng ignorerer tekstkomponenten ved trykk hendelse handler. Heldigvis er dette en enkel løsning. 

// I design-system / type / Type.js eksport const Type = (tag = 'span', size = TextSize.default, boldness = TextBold.default, barn, className = ", align = TextAlign.default, ... resten ) => const Tag = '$ tag'; const classNames = 'ds-text $ size $ boldness $ align $ className'   barn  ; 

ES6 har en praktisk måte å trekke ut de gjenværende parameterne som en matrise. Bare bruk det og spred dem over til komponenten.

Å gjøre komponenter oppdagelig

Som teamet ditt er det vanskelig å få alle til å synkronisere alle komponentene som er tilgjengelige. Historiebøker er en fin måte å gjøre komponentene dine synlige. La oss sette opp en grunnleggende storybook-komponent. 

For å komme i gang, kjør:

npm i -g @ storybook / cli getstorybook

Dette setter opp den nødvendige konfigurasjonen for storybooken. Herfra er det en kino å gjøre resten av oppsettet. La oss legge til en enkel historie for å representere forskjellige tilstander av Type

Import Reakt fra 'reagere'; importer storiesOf fra '@ storybook / react'; importer Type, TextSize, TextBold fra '... /design_system/type/Type.js'; historierOf ('Type', modul) .add ('standard tekst', () => (  Lorem ipsum  )). Legg til ('fet ​​skrift', () => (  Lorem ipsum  )). Legg til ('header text', () => (  Lorem ipsum  )); 

API-overflaten er enkel. storiesOf definerer en ny historie, vanligvis komponenten din. Du kan da opprette et nytt kapittel med Legg til, å vise frem de forskjellige tilstandene til denne komponenten. 

Selvfølgelig er dette ganske grunnleggende, men storybooks har flere tilleggsprogrammer som vil hjelpe deg å legge til funksjonalitet til dine dokumenter. Og nevnte jeg at de har emoji-støtte? 😲

Integrere med et arkiv for hylleutforming

Å designe et design system fra grunnen av, er mye arbeid og kan ikke være fornuftig for en mindre app. Men hvis produktet ditt er rik og du trenger mye fleksibilitet og kontroll over hva du bygger, kan du sette opp ditt eget brukerbibliotek på lengre sikt. 

Jeg har ennå ikke sett et bra UI-komponentbibliotek for React. Min erfaring med reaksjonsstart og material-ui (biblioteket for React, det vil si ikke rammen selv) var ikke bra. I stedet for å gjenbruke et helt UI-bibliotek kan det være fornuftig å velge enkelte komponenter. For eksempel er implementering av multi-select et komplekst brukerproblem, og det er tonnevis av scenarier å vurdere. For dette tilfellet kan det være enklere å bruke et bibliotek som React Select eller Select2.

Et ord med forsiktighet, skjønt. Enhver ekstern avhengighet, spesielt brukergrensesnitt, er en risiko. De er forpliktet til å endre sine APIer ofte, eller på den annen side, fortsette å bruke gamle, avskrevne funksjoner i React. Dette kan påvirke din teknisk levering, og eventuelle endringer kan være kostbare. Jeg anbefaler at du bruker en wrapper over disse bibliotekene, så det blir enkelt å erstatte biblioteket uten å berøre flere deler av appen.

Konklusjon

I dette innlegget har jeg vist deg noen måter å dele appen din i atomvisuelle elementer ved å bruke dem som Lego-blokker for å oppnå ønsket effekt. Dette gjør det mulig å gjenbruke og vedlikeholde kode, samt gjøre det enkelt å opprettholde et konsekvent brukergrensesnitt i hele appen din.

Del dine tanker om denne artikkelen i kommentarfeltet!