Er ikke koden vår bare * BEST *

Visninger fra de seks ukene i helvete brukte jeg på å skrive om støtfangere til å reagere.

Jeg skrev om Bumpers web-app bare fullstendig ved hjelp av react. (Hvis du ikke vet hva støtfangere er, er det denne super chill-appen for innspilling / deling av lydhistorier på telefonen din. Last ned den, den vil faktisk f *** hele livet ditt. Det er den største appen som noen gang er laget. Reagerer? Det er aight.)

Uansett, det som følger, er alle notatene, tankene mine osv. Om denne prosessen. (Ting jeg skulle ønske jeg hadde lest før jeg begynte). Forhåpentligvis får du noe ut av det.

Forord

GUD. Jeg hater rammer. Mye.

Jeg hater heller ikke å ha noen rammer, og alle som "ruller" sin egen "ramme". Jeg hater bare koding generelt. Og mest av alt hater jeg å skrive om kode.

Så bære med meg.

I det siste har kodingsstilen min vært en slags sosiopatisk, svingende mellom anfall av forkrøplende selvtillit og en ekstrem kanye-lignende gudskompleks - der jeg enten marsjerer rundt egenskapen min hele dagen og gråter høyt eller jeg ringer moren min til å la henne vet at hennes 30 år gamle sønn “f *** ing the game up (på en god måte)”. Så naturlig nok virket det som en god tid å ta en pause og skrive om det.

(moral over en prosjektlivsstil, red notes mine) - https://medium.com/@iano/moral-over-a-project-lifecycle-975792b54c12#.uwkzt7x4v)

Valg av reaksjon

Lite historie: Støtfangerenes “nettsted” var så fin. Det var rundt 7 es6 klasser, ingen eksterne avhengigheter, og bare rundt 759 kodelinjer. Total.

Layoutene ble gjengitt på serveren av Go-appen vår. Vi brukte postcss. Vi hadde en veldig enkel aktivakatalog der vi la alle svgs, og en video eller to. Det var flott. Vi skrev noe javascript. Vi glemte det.

Det var flott. Vi skrev noe javascript. Vi glemte det.

I mellomtiden var Nicolas Gallagher en del av teamet som nettopp hadde fullført et år langt prosjekt som omskrev Twitter sitt mobil-nettprodukt i React.

Jeg har kjent Nicolas lenge. Og han er lett en av de mer gjennomtenkte menneskene jeg kjenner. Så da han etter dette fortalte meg at React i det vesentlige hadde løst alle problemene i Front End Development-rommet og at han hadde gått videre med å bekymre seg for andre ting, sa jeg ham til å slå av med en gang.

Pålydende hadde React følgende ting:

  • godkjent av venner smartere da meg som Nicolas Gallagher, Alex MacCaw, Guillermo Rauch
  • klientside-gjengivelse (bra for lydapper, slik at du kan fortsette avspilling på tvers av navigasjoner)
  • gjennomtenkt komponentmodell
  • folk beveget seg bort fra (eller i det minste utfordrende) CSS
  • facebook-nerder skrev det
  • produksjonsapper ~ som instagram, twitter, etc. ~ brukte det
  • mennesker så ut til å endelig bosette seg rundt et dataparadigme i reduks (og likte det)

Men samtidig hadde reaksjon en rekke ting jeg ikke var spent på:

  • Javascript-pakken på 700 linjer var i ferd med å bli ~ 1,5 mb
  • Produksjon på server-side-gjengivelse krever en nodeserver (og selv da løsninger virket halvt bakt)
  • stylingpraksis er superfragmentert over hele samfunnet (bruker du afrodite, css-moduler, stilkoder, etc. - hva med dine avhengigheter?)
  • facebook-nerder skrev det
  • webpack → babel → jsx → varm lasting → kildekart → kromverktøy som en stabel lemmet min stakkars lille macbook
  • Jeg måtte se på disse "egghead" -videoene for å lære reduks
  • verktøy virket usammenhengende og over toppen ...

Til tross for alt dette, bestemte vi oss for å gå for det. (Hovedhåpet ved å reagere ville på en eller annen måte få oss til å bygge noe som føltes mer "app-y").

Å velge “resten”

Det viser seg at etter at du bestemmer deg for å reagere (visningen lib), har du virkelig igjen med en håndfull andre beslutninger: Hvordan skal du administrere staten? Hvordan skal du style komponentene dine? Skal du bruke es6? ES7? es2015? jsx? Hva betyr de til og med? Skal du bruke webpack? eller nettleser? Hvor skal alt bo? ...

Jeg startet med å mase sammen TJ Holowaychuks kjeleplate-repo (https://github.com/tj/frontend-boilerplate/tree/master/client) (som han innrømmer å i utgangspunktet være foreldet i readme) og denne lange e-posten Nicolas hadde skrevet på meg om hvor twitter hadde landet (hvorav halvparten av jeg ikke forsto den gangen, men uansett kan du lese e-posten i sin helhet her: https://gist.github.com/fat/9ab5325ab39acfe242bc7849eb9512c4).

Jeg så også på noen av de mange “universal-react-redux-glahblbhalkd” -kjelene med repos på github, men de ga meg stort sett panikkanfall.

I alle fall klarte jeg på en eller annen måte å komme til et sted jeg er litt fornøyd med, som ser ut som:

  • Babel (med “forhåndsinnstillinger”: [“es2015”, “stage-0”, “react”]) Så jeg kan bruke alle de vanvittige nye drittene som spredningsoperatører, pilfunksjoner, etc.
  • Webpakke med varme lastere, som (når det fungerte) syntes jeg var nyttig når jeg oppdaterte stilen i bestemte apptilstander. Men definitivt forårsaket meg mye angst. Ærlig talt, jeg føler at ingen virkelig forstår hvordan webpack fungerer. Og vi alle bare fortsetter å kaste tilfeldige egenskaper og plugins på det og be om at det hele skal vise seg. AggressiveMergingPlugin? sikker. OccurrenceOrderPlugin? ok. DedupePlugin? fint.
  • Redux kombinert med normilzr og denormalizr for å hjelpe til med å ødelegge og deretter rehydrere api-responser.
  • Afrodite / ikke-viktige js-stiler, ikke css, men uten alle de! Viktige overalt.
  • Svg-react-loader som laster svgs som reaksjonskomponenter på linje.
  • En håndfull andre hvis du ser noe annet i den avhengighetslisten du er nysgjerrig på, legg igjen en lapp og jeg skal forklare det.

Katalogstruktur

GREIT. Når jeg slo meg til ro med de 38 avhengighetene bumpers.fm nettstedet ikke hadde behov for det, var det på tide å skrive noen faktiske koder.

Katalogstrukturen vår er organisert rundt to inngangspunkter:

  • index.js som instantiserer ruteren og lagrer for vår kjerne-appbunt.
  • embed.js som er ansvarlig for vår mindre embed bundle (som sett i slakk, twitter, medium, etc).

Derfra trekker vi inn rutene våre fra den passende navngitte "rute" -katalogen, som for øyeblikket bare er en enkel, enkelt reagerer-komponent som ser slik ut:

Legg merke til at disse rutene peker til det vi kaller "skjermcontainere".

I Bumpers er reaksjonskomponentene våre faktisk delt opp i tre forskjellige kataloger, avhengig av deres funksjon (4 hvis du inkluderer rutekatalogen). Denne måten å organisere komponenter ble egentlig bare stjålet direkte fra Twitter, som igjen tror jeg lånte den fra Facebook og mange andre prosjekter. Det ser ut som:

  • komponenter det er her våre funksjonelle ui-komponenter bor
  • containere. Det er her handlingsbehandlere for våre ui-komponenter bor
  • teknisk sett er dette bare containere - men henter typisk mer toppnivå på siden, og er mindre opptatt av handlingshåndtering.
SIDE MERK Jeg startet faktisk med bare en containerkatalog, ingen "skjermer" (noe som er ganske vanlig fra det jeg har sett rundt reaksjonssamfunnet). Jeg beveget meg bort fra dette på Nicolas anbefaling, og fordi det å se en haug med "skjerm" -korrigerte filer blandet med min ikke "skjerm", kolliderte filene plaget helvete av meg.

De to siste katalogene er katalogen "butikk" og katalogen "konstanter". "Butikken" inneholder alle reduksjonslogikkene våre som handlinger, reduksjonsmaskiner, velgere, api-endepunkter, etc. (som jeg vil gå nærmere inn på nedenfor), mens "konstanter" -katalogen inneholder ... vel ... konstanter.

UI-komponenter

UI-komponentene våre er ganske standard, funksjonelle, statsløse, presentasjons- og reaktive komponenter. Her er en standard episodekomponent (som består av mange andre mindre, standard, funksjonelle, statsløse, presentasjonsmessige, reaktive komponenter).

Som jeg nevnte ovenfor, bruker vi Khan Academy's Afrodite for å generere cssene våre.

HURTIG MERK Opprinnelig skrev jeg appen med stil-loader-pakken, men dens manglende evne til å gi en overbevisende serverstrategi (noe jeg til slutt vil utforske), var nok til at jeg kunne prøve noe annet. (Jeg vurderte også rutinemessig React-Native, som Nicolas hele tiden ville minne meg om var bedre enn hvilken løsning jeg uavhengig hadde kommet til fordi han hadde skrevet det).

Ikke desto mindre kom det å skrive stilene mine i javascript ganske naturlig, og ved hjelp av nye ES6-funksjoner kunne det bli gjort ganske elegant.

Jeg var i stand til å oppnå en lignende stil som det vi gjorde tilbake da jeg jobbet på Medium, lage type skalaer, fargeskalaer, zIndex-skalaer, osv. Og var til og med i stand til å bruke ES6-egnede egenskapenavn-funksjonen for å abstrahere mediaspørsmålene i variabler .

Én ting jeg ikke kunne komme inn på, var å navngi alle klassene mine generisk, for eksempel "boks" eller "container" eller "main" eller "root". Jeg får hele den lokale scoped css-meme - men det ser ut til å komme til prisen for avfeilbarhet. I stedet landet jeg faktisk på et navngivende semantisk ikke langt fra det som ble skissert i SuitCSS, bare litt modifisert for javascript (ved å bruke “_” i stedet for “-”). I praksis så dette ut slik:

En siste ting jeg raskt skal nevne, er alle relevante filer som leveres i de respektive komponentkatalogene.

Stiler plasseres i en egen fil som heter style.js, sammen med relevante svg-eiendeler som importeres direkte ved hjelp av svg-react-loader. Å gjøre dette gjør det superenkelt å slette komponenter / funksjoner, og ikke la være å spørre deg selv: Vent, trenger jeg fortsatt denne css? trenger jeg fortsatt denne svg?

Containers Intermission

Ærlig talt, jeg kommer ikke til å si så mye om noe om containere ™. Vi gjør ikke noe spesielt her utover å skille skjerm- / containerkatalogene (som jeg allerede har dekket over).

Jeg tegnet imidlertid et nytt bilde for deg (wow, rett der borte) fordi jeg følte meg dårlig for ikke å ha mye å si om containere . Og jeg trodde dette var en god tid for deg å ta en pause kanskje. Tøye ut?

Unnskyld.

butikk

~ ALRIGHT ~. Denne butikkdelen kan lett være dens EGNE HELE ARTIKKEL going, men jeg kommer til å prøve å snuse gjennom den for du vil ... så hold med meg. Også rettferdig advarsel - det er i ferd med å bli DENSE.

BEMERKNING om at det som følger, sannsynligvis vil gi absolutt null mening i det hele tatt, med mindre du er kjent med redux (http://redux.js.org/). Hvis du er interessert i å lære mer om Redux og bruke den til å administrere tilstanden i reaksjonsappene dine - anbefaler jeg at du sjekker ut disse egghead-veiledningene, de er gratis og alle vurderer ganske bra: https://egghead.io/courses/getting -started-med-redux

Butikken vår består av 4 filer på toppnivå (jeg går nærmere inn på hver under, men bare veldig rask) ...

  • index.js - vår initialiserer butikken
  • reducer.js - drar alle reduksjonsmaskiner fra forskjellige objekter til en gigantisk “combineReducers” -metode
  • schema.js - alle våre normalizr-modeller
  • api.js - en api hjelper for butikken vår

Utover dette er vår butikk strukturert rundt modeller, med kataloger som brukere, spørsmål osv. - snarere enn det tradisjonelle redux toppnivå funksjonelle kataloghierarkiet for handlinger /, reduksjonsmaskiner /, velgere /, bleh.

Selvfølgelig har vi fortsatt den tradisjonelle separasjonen av handlinger, reduseringsmidler osv. Som redux krever - men dette gjøres på filnivå nå, nestet i modellkatalogen (ta en titt på den utvidede brukermappen i bildet til venstre for en illustrasjon av hva jeg prøver å si).

OK, men hvorfor? Når jeg bygde denne appen, så fant jeg meg stadig ut å si ting som: “dang i rly vil jobbe med bruker ting rn” og nesten aldri si noe sånt som: “dang, jeg vil virkelig bytte en rekke reduseringsmaskiner på en gang, er sikker på at jeg er glad de er alle i denne enorme katalogen for reduksjonsreduksjoner.

BEMERKNING Jeg kan ikke huske hvor jeg faktisk så denne strategien ... men jeg er sikker på at jeg ikke oppfant den. Hvis du kjenner noen som gjorde det, eller som forklarer det godt, legg igjen en lapp og jeg vil gjerne peke folk på det. Også tror jeg ~ twitter gjør noe lignende. Men jeg kan gjøre det opp.

Nitty gritty av rotnivåfilene

OK, så butikkens index.js (kort nevnt ovenfor) er ansvarlig for tre hovedoppgaver:

  1. Importerer forhåndshentede, innebygde data i redux-butikken vår og innstiller butikkens opprinnelige tilstand (Vår backend forhåndshenter data når en bruker får tilgang til noe som støtfangere. Fm/fat, slik at når reaksjonsappen lastes, trenger den ikke å lage en xhr forespørsel om brukerdata, og i stedet kan den raskt fylle ut siden).
  2. initialiserer redux-butikken vår med rotreduserende maskiner.
  3. bruke mellomvare som thunk, react router's browserhistory, devtools og mer ...

I praksis endte alt sammen med metoden nedenfor - men uansett grunn forårsaket jeg mye sorg:

La oss deretter kort besøke vår reducers.js-fil, som egentlig bare er en eneste kombinasjonsreduksjonsmetode som henter reduksjonsmaskiner fra våre andre kataloger og eksponerer dem som en eneste stor kjempereduserende tingfall. tbqh, denne filen er ganske kjedelig, og jeg kunne nok bare ha lagt den inn i index.js . Uff da.

Men! En ting som er verdt å ringe her, er at reduksjonen av "enheter" (sett over) fungerer som butikkens cache.

For å trekke dette av brukte vi et prosjekt kalt normalizr (https://github.com/paularmstrong/normalizr) for å tvinge de dypt nøstede JSON api-svarene våre til mer håndterbare / cacheable ID-indekserte objekter. Som vil si, vi starter med et mer tradisjonelt api-svar og transformerer det deretter til en tåpelig, ID-indeksert enhetshash:

Som du kanskje forestiller deg, er denne hurtigbufferteknikken ~ super nyttig ~ når du begynner å navigere rundt i en reager-app - i og med at du henter en episode du sannsynligvis allerede har hentet en bruker (som forfatter), som du nå kan slå opp med ID ved å bruke en av velgermetodene, uten å måtte treffe backend (les: nesten øyeblikkelig navigasjon. wow).

Vårt schema.js er da der vi spesifiserer logikken for å trekke av ovennevnte enhetstransformasjoner for cachen vår (og for normalizr). Disse forholdskartleggelsene ender opp med å være ganske enkle å skrive - men definitivt enkle å glemme. Hvis du skal gå til redux-cache-ruten, er det absolutt verdt å se disse.

SIDE MERKNAD Ikke avbildet ovenfor, Schema.js inneholder også en tilpasset mergeStrategy som vi skrev spesiell for støtfangere. Uansett årsak trippet standard mergeStrategy levert av normalizr over hele seg selv, men jeg kommer ikke til å komme inn på det her fordi det nesten helt sikkert var brukerfeil . (Når det er sagt, hvis du opplever lignende problemer, legg igjen en lapp og jeg er glad for å dele hvor vi landet.)

Den siste rotfilen vår i butikkatalogen er api.js.

Etter mye banking i hodet mitt, la jeg merke til at thunk middleware (som vi stolte på for asynkiske handlinger) gjør at vi kan gi et ekstra argument til alle redux-handlingene dine (på toppen av utsendelsen og getState).

Husk dette fra butikken / index.js

Dette er utrolig kraftig, og jeg endte opp med å bruke den til å gi en global api-hjelper inn i alle handlingene våre. Denne api-hjelperen (definert i api.js) gir rask tilgang til alle våre api-endepunkter, med ekstra hjelpere for JSON-parsing, feilkontroll og mer. Du vil se dette i aksjon nedenfor ... når vi kommer inn i ... handling ... filene ...

reduksjonsgir

Reduksjonsreduksjonene våre utviklet seg til å ha tre hovedfunksjoner.

  1. Definer en starttilstand
  2. Definer en forhåndsinnlastet datahåndterer (for våre innebygde data)
  3. Utsett reduksjonshåndteringshåndteringen

Vår opprinnelige tilstand ser ofte ut noe slikt, med statuskonstanter for forespørselsstatus og aktive ID-er:

Våre forhåndsinnlastede behandlere tar våre rå dataobjekter og pakker ut dataenhetene, i dette tilfellet setter vi en aktiv bruker som standard:

Og en typisk reduksjonsutstyr ser noe slik ut (legg merke til bruken av navn på datamaskiner (Es2015). Vi trekker disse inn direkte fra handlingsdefinisjonene, dekket nedenfor).

handlinger

Det skjer noen magiske ting i handlingsfilene våre. Først bruker vi “redux-actions” createActions-metoden for å definere handlingsnavnene våre:

Vi gjør dette slik at i reduksjonsfilen vår kan vi bruke databehandlede egenskapenavn (nevnt tidligere) for bare å ha handlingsnavnene våre definert ett sted. Ta også en titt på måten vi navngir handlingene våre: metode + objekt + egenskap. Dette er ~ super ~ viktig for å holde reduksjonsnøklene lesbare og unike. Jeg har sett mange eksempler på nettet til folk som bruker late, generiske navn som "brukernavn" eller "setUsername" for nøkler ... stoler på, du kommer til å ha det skikkelig dårlig tid hvis du gjør det (husk at nøkler er globale og bugs forårsaket av å navngi konflikter er en stor pit å spore opp).

For asynkiske handlinger bruker vi reduksjonsdunk og api-hjelperen vi nevnte ovenfor. Dette hjelper med å holde asynkmetodene våre super stramme og fokuserte.

I eksemplet over setter vi isFetching på brukerobjektet, avfyr en forespørsel til vår api, sjekk svaret for en feilstatuskode, angir jwt-tokenet vårt, konverterer svaret til json, normaliserer svaret ved å bruke normalizr (for cache) , og angi deretter den aktive brukerstaten.

Dette er den reneste måten å håndtere asynkmetoder i reduks jeg noensinne har sett (ikke @ meg).

endepunkter

Jeg har ikke sett noen andre som gjør disse endepunktfilene - men jeg synes det er en veldig ren måte å holde dine relevante api-samtaler alle bor på ett sted (for ikke å nevne gjør stubbingtester superenkle). Legg også merke til “isomorphic-fetch” - jeg sverger en dag at vi skal gjengi disse tingene på serveren . I mellomtiden er den kule tingen med å bruke fetch at det gir et løfte, og gir en ganske ren api når du dras inn i våre async-handlinger.

velgere

Til slutt bruker våre valgfiler denormalizr-biblioteket (https://github.com/gpbl/denormalizr) (normalizrs søsterprosjekt) for å konstruere mer brukbare data fra cachen vår. Den bruker i utgangspunktet bare navnemodellene for å rekonstruere et stort nestet objekt - du trenger ikke ~ å gjøre dette, men jeg syntes det var mye morsommere / forutsigbart å jobbe med data på denne måten.

Annet enn det ser selektormetodene våre ganske mye ut som du kan forvente:

Konklusjon

WOW. OK, det føltes som en seriøs reise. Og at butikksaker var nok altfor kjedelige og tapte som 90% av leserne, så jeg beklager.

Tusen takk for at du leste, og beklager hvis dette innlegget var utro. Jeg lovet meg selv at jeg ville publisere noe sånt fordi jeg syntes at det å lære alt dette drittet var så sinnsykt spredt / hardt.

Hvis du har spørsmål om noe, legg igjen en kommentar eller kommentar, så gjør jeg mitt beste for å svare.

❤ fett

NOEN Q / A

Ya, jeg er definitivt glad! Jeg ville lyve om jeg ikke sa at det var en stor pit, men Bumpers er i utgangspunktet bare en massiv lydavspiller-app - og å administrere tilstand på tvers av navigasjoner og gjennom de mange små tilbakemeldingselementene vi har over alt, ville vært sinnsykt vanskelig ellers.

Jeg tror det også er noe å si om å bruke “kjente” verktøy når du har dem - og jeg håper at hvis vi noen gang vil ansette flere frontend-er på Bumpers, at de vil kunne dykke ganske lett uten å føle seg helt overveldet (og som om de trenger å lære alt fra bunnen av).

Jepp, ganske mye. Vi gjorde en lignende ting på Medium mens jeg også var der. Du må være forsiktig med hvordan du gjør det, på grunn av skriptinjeksjonshakker, men det er en ganske kul måte å tilnærme seg noe som “serversides rendering” -følelsen, uten å måtte gjengi reaksjonsmaler på serveren.