Er ikke vores kode bare * BEST *

Synspunkter fra de 6 uger i helvede brugte jeg omskrivning af stødfangere i reaktion.

Jeg omskrev bare Bumpers webappen ved hjælp af react. (Hvis du ikke ved, hvad det er stødfangere, er det denne super chill-app til optagelse / deling af lydhistorier på din telefon. Download den, den vil faktisk f *** hele dit liv. Det er den største app, der nogensinde er lavet. Reager? Det er aight.)

I det følgende er alle mine notater, tanker osv. Om denne proces. (Ting, jeg ville ønske jeg havde læst før jeg startede). Forhåbentlig får du noget ud af det.

Forord

GUD. Jeg hader rammer. En masse.

Jeg hader også ikke at have en ramme, og nogen, der "ruller" deres egen "ramme". Jeg hader bare bare kodning generelt. Og mest af alt hader jeg at skrive om kode.

Så bær med mig.

På det seneste har min kodningstil været en slags sociopatisk, svingende mellem pasninger af lammende selvtillid og et ekstremt kanye-lignende gudskompleks - hvor jeg enten marsjerer rundt om min apt alene hele dagen og græder højt, eller jeg ringer til min mor til at lade hende ved, at hendes 30 år gamle søn "fokker spillet op (på en god måde)". Så naturligvis virkede det som et godt tidspunkt at tage en pause og skrive om det.

(moral over en projektlivsstil, røde toner mine) - https://medium.com/@iano/moral-over-a-project-lifecycle-975792b54c12#.uwkzt7x4v)

Valg af reaktion

Lidt historie: Stødfangernes “website” var så rart. Der var ca. 7 es6 klasser, ingen eksterne afhængigheder og kun ca. 759 kodelinjer. I alt.

Dens layout blev gengivet på serveren af ​​vores Go-app. Vi brugte postcss. Vi havde et rigtig enkelt aktivmappe, hvor vi lagde alle vores svgs og en video eller to. Det var godt. Vi skrev noget javascript. Vi glemte det.

Det var godt. Vi skrev noget javascript. Vi glemte det.

I mellemtiden var Nicolas Gallagher en del af teamet, der netop havde afsluttet et år langt projekt, der skrev om Twitter's mobil-web-produkt i React.

Jeg har kendt Nicolas i lang tid. Og han er let en af ​​de mere tankevækkende mennesker, jeg kender. Så da han efter dette fortalte mig, at React i det væsentlige havde løst alle problemerne i Front End Development-rummet, og at han var gået videre med at bekymre sig om andre ting, sagde jeg ham til at slå med det samme.

Pålydende havde React følgende ting:

  • godkendt af smartere venner, så mig som Nicolas Gallagher, Alex MacCaw, Guillermo Rauch
  • gengivelse fra klientsiden (godt til lydapps, så du kan fortsætte afspilningen på tværs af navigationer)
  • tankevækkende komponentmodel
  • folk bevæger sig væk fra (eller i det mindste udfordrende) CSS
  • facebook nørder skrev det
  • produktionsapps ~ som instagram, twitter osv. ~ brugte det
  • folk syntes endelig at slå sig ned omkring et dataparadigme i redux (og kunne lide det)

Men på samme tid, reaktion havde en række ting, jeg ikke var begejstret for:

  • Mit 700-linjers javascript-bundt var ved at blive ~ 1,5 mb
  • produktion på server-side-gengivelse kræver en nodeserver (og selv da syntes løsninger halvt bagt)
  • styling praksis er superfragmenteret på tværs af samfundet (bruger du afrodite, css-moduler, stil tags osv. - hvad med dine afhængigheder?)
  • facebook nørder skrev det
  • webpack → babel → jsx → hot loading → kildekort → krom værktøjer som en stak lammede min stakkels lille macbook
  • Jeg var nødt til at se disse f *** ing “egghead” videoer for at lære redux
  • værktøj virkede usammenhængende og over toppen ...

På trods af alt dette besluttede vi at gå efter det. (Det vigtigste håb, der reageres, ville på en eller anden måde sætte os op til at bygge noget, der føltes mere "app-y").

Valg af "resten"

Det viser sig, at når du har besluttet at reagere (din visningslib), har du virkelig siddet med en håndfuld andre beslutninger: Hvordan skal du styre staten? Hvordan skal du style dine komponenter? Skal du bruge es6? ES7? es2015? JSX? Hvad betyder disse endda? Skal du bruge webpack? eller browserificere? Hvor skal alt bo? ...

Jeg begyndte med at slå sammen TJ Holowaychuk's kedelplade repo (https://github.com/tj/frontend-boilerplate/tree/master/client) (som han indrømmer at dybest set være forældet i readme) og denne lange e-mail, som Nicolas havde bundet til mig om, hvor twitter var landet (hvoraf halvdelen jeg ikke forstod på det tidspunkt, men uanset kan du læse e-mailen i sin helhed her: https://gist.github.com/fat/9ab5325ab39acfe242bc7849eb9512c4).

Jeg kiggede også på et par af de mange “universal-react-redux-glahblbhalkd” kedelplader repos på github, men de gav mig stort set alle panikanfald.

Under alle omstændigheder lykkedes det mig på en eller anden måde at komme til et sted, jeg er lidt tilfreds med, der ligner:

  • Babel (med “forudindstillinger”: [“es2015”, “stage-0”, “react”]) Så jeg kan bruge alle de vanvittige nye lort som spredningsoperatører, pilefunktioner osv.
  • Webpakke med hot loaders, som (når det fungerede) fandt jeg praktisk, når jeg opdaterede stil i bestemte apptilstande. Men bestemt forårsagede mig masser af angst. Helt ærligt føler jeg, at ingen virkelig forstår, hvordan webpack fungerer. Og vi alle bare fortsætter med at kaste tilfældige egenskaber og plugins på det og bede om, at det hele viser sig. AggressiveMergingPlugin? jo da. OccurrenceOrderPlugin? Okay. DedupePlugin? bøde.
  • Redux kombineret med normilzr og denormalizr for at hjælpe med destruktion og derefter rehydrering af api-reaktioner.
  • Afrodite / ikke-vigtige js-stilarter, ikke css, men uden alle disse! Vigtige overalt.
  • Svg-react-loader, der indlæser svg'er som reaktionskomponenter inline.
  • En håndfuld andre, hvis du ser noget andet på den afhængighedsliste, du er nysgerrig efter, læg en note, så forklarer jeg det.

Katalogstruktur

OKAY. Da jeg først besluttede mig med de 38 afhængighedsbuffere.fm webstedet ikke havde krævet, var det på tide at skrive noget faktisk kode.

Vores biblioteksstruktur er organiseret omkring to indgangspunkter:

  • index.js, som instantierer routeren og gemmer til vores centrale app-bundt.
  • embed.js som er ansvarlig for vores mindre embed bundt (som det ses i slack, twitter, medium osv.).

Derfra trækker vi vores ruter ind fra det rigtigt navngivne “rute” -katalog, som i øjeblikket kun er en enkel, enkelt reaktions-router-komponent, der ser sådan ud:

Bemærk, at disse ruter peger på det, vi kalder "skærmcontainere".

I Bumpers er vores reaktionskomponenter faktisk opdelt i 3 forskellige mapper, afhængigt af deres funktion (4, hvis du inkluderer rutebiblioteket). Denne måde at organisere komponenter på var grundlæggende bare stjålet direkte fra Twitter, som til gengæld tror jeg lånte den fra Facebook og masser af andre projekter. Det ser ud som om:

  • komponenter det er her vores funktionelle ui-komponenter bor
  • containere, det er her handlingsanlæg til vores ui-komponenter bor
  • skærme teknisk set er dette kun containere - men typisk udfører mere side på topniveau og er mindre optaget af handlinghåndtering.
BEMÆRK SIDE Jeg startede faktisk med bare et containermappe, ingen "skærme" (hvilket er temmelig almindeligt fra det, jeg har set omkring reaktionsfællesskabet). Jeg flyttede væk fra dette på Nicolas anbefaling, og fordi det at se en flok "skærm" -korrigerede filer blandet med min ikke "skærm" kæmpede filerne fik helvede ud af mig.

De sidste to mapper er biblioteket "butik" og "konstanter" -kataloget. "Butikken" indeholder alle vores redux-logik som handlinger, reduktionsmaskiner, vælgere, api-endpoint osv. (Som jeg vil gå nærmere ind på nedenfor), mens "konstanterne" -mappen indeholder ... godt ... konstanter.

UI-komponenter

Vores UI-komponenter er temmelig standard, funktionelle, statsløse, præsentative, reaktive komponenter. Her er en standardafsnitskomponent (som består af mange andre mindre, standard, funktionelle, statsløse, præsentative, reaktive komponenter).

Som jeg nævnte ovenfor bruger vi Khan Akademis Aphrodite til at generere vores css.

KORT BEMÆRK Oprindeligt skrev jeg appen ved hjælp af stil-loader-pakken, men dens manglende evne til at tilvejebringe en overbevisende serverstrategi (noget, som jeg til sidst vil udforske), var nok for mig til at prøve noget andet. (Jeg overvejede også rutinemæssigt React-Native, som Nicolas konstant ville minde mig om var bedre end hvilken løsning jeg uafhængigt var kommet til, fordi han havde skrevet det).

Ikke desto mindre kom skrivning af mine stilarter i javascript temmelig naturligt, og ved hjælp af nye ES6-funktioner kunne det gøres temmelig elegant.

Jeg var i stand til at opnå en lignende stil, som vi gjorde tilbage, da jeg arbejdede på Medium, skabte type skalaer, farve skalaer, zIndex skalaer osv. Og var endda i stand til at bruge funktionen ES6-beregnet egenskabsnavne til at abstrahere mine medieforespørgsler i variabler .

Én ting, jeg ikke kunne komme ind på, var at navngive alle mine klassenavne generisk, f.eks. "Boks" eller "container" eller "hoved" eller "rod". Jeg får hele den lokale scoped css-meme - men det ser ud til at komme til prisen for fejlfinding. I stedet landede jeg faktisk på et navngivende semantisk ikke langt fra det, der blev skitseret i SuitCSS, bare let modificeret til javascript (ved hjælp af “_” i stedet for “-”). I praksis så det sådan ud:

En sidste ting, jeg hurtigt vil nævne, er alle vores relevante filer live i deres respektive komponentkataloger.

Styles placeres i en separat fil med navnet style.js sammen med relevante svg-aktiver, der importeres direkte ved hjælp af svg-react-loader. Ved at gøre dette er det super nemt at slette komponenter / funktioner og ikke konstant lade være med at spørge dig selv: Vent, har jeg stadig brug for denne css? har jeg stadig brug for denne svg?

Containers Intermission

Helt ærligt, jeg vil ikke sige meget om noget om containere ™. Vi gør intet særligt her ud over at adskille skærm- / containermapper (som jeg allerede har dækket ovenfor).

Jeg tegnede dog et andet billede til dig (wow, lige derovre), fordi jeg følte mig dårlig for ikke at have meget at sige om containere . Og jeg troede, at dette var et godt tidspunkt for dig at tage en pause måske. Strække?

Undskyld.

butik

~ ALRIGHT ~. Denne butiksafsnit kunne let være dens EGNE HELE ARTIKEL , men jeg vil prøve at slog igennem det for dig, så bær med mig. Også fair advarsel - det er ved at blive DENSE.

BEMÆRK, hvad der følger, sandsynligvis vil give absolut nul forstand overhovedet, medmindre du er bekendt med redux (http://redux.js.org/). Hvis du er interesseret i at lære mere om Redux og bruge den til at administrere tilstand i dine reage-apps - jeg anbefaler, at du tjekker disse egghead-tutorials, de er gratis og alle overvejer temmelig godt: https://egghead.io/courses/getting -started-med-redux

Vores butik består af 4 filer på øverste niveau (jeg går nærmere ind på hver nedenfor, men bare rigtig hurtig) ...

  • index.js - vores butiksinitialisator
  • reducer.js - trækker alle reduktionsredskaber fra forskellige objekter til en kæmpe “combineReducers” -metode
  • schema.js - alle vores normalizr-modeller
  • api.js - en api-hjælper til vores butik

Ud over dette er vores butik struktureret omkring modeller med mapper som brugere, promp osv. - snarere end det traditionelle redux på topniveau funktionelt kataloghierarki af handlinger /, reducere /, vælgere /, bleh.

Selvfølgelig har vi stadig den traditionelle adskillelse af handlinger, reduceringer osv., Som redux kræver - men dette gøres på filniveau nu, indlejret i dets modelmappe (se på den udvidede brugermappe i billedet til venstre for en illustration af hvad jeg prøver at sige).

OK, men hvorfor tho? Ved opbygningen af ​​denne app fandt jeg mig konstant ved at sige ting som: “dang i rly vil arbejde på bruger ting rn” og næsten aldrig sige noget som: “dang, jeg rly vil ændre en masse reduktionsmaskiner på én gang, er bestemt glad for de er alle i dette massive katalog med reducerende reduktionsgear ”.

SIDEBEMÆRK Jeg kan ikke huske, hvor jeg faktisk først så denne strategi ... men jeg er sikker på, at jeg ikke opfandt den. Hvis du kender nogen, der gjorde det, eller som forklarer det godt, skal du lægge en note, og det vil jeg med glæde henvise folk til. Også tror jeg ~ twitter gør noget lignende. Men det kunne jeg gøre.

Nitty gritty af rodniveaufilerne

Okay, så butikens index.js (kort nævnt ovenfor) er ansvarlig for 3 hovedopgaver:

  1. Importerer forudindhentede, indlejrede data i vores redux-butik og indstiller butikens oprindelige tilstand (Vores backend forudindhenter data, når en bruger får adgang til noget som stødfangere. Fm/fat, så når reaktions-appen indlæses, behøver den ikke straks at oprette en xhr anmodning om brugerdata, og i stedet kan den hurtigt hurtigt udfylde siden).
  2. initialiserer vores redux-butik med vores rodreduktionsredskaber.
  3. anvende middleware som thunk, react router's browserhistorie, devtools og mere ...

I praksis endte det hele med at lignede metoden nedenunder - men uanset hvad der forårsagede mig en masse sorg:

Lad os derefter kort besøge vores reducers.js-fil, som i det væsentlige kun er en enkelt combinReducers-metode, der trækker reduktionsredskaber fra vores andre mapper og udsætter dem som en enkelt kæmpe reducer-vandfald ting. tbqh, denne fil er temmelig kedelig, og jeg kunne sandsynligvis lige have lagt den ind i index.js . hovsa.

Imidlertid! En ting, der er værd at kalde her, er, at vores "enheder" -reducerende værktøj (set ovenfor) fungerer som vores butiks cache.

For at fjerne dette brugte vi et projekt kaldet normalizr (https://github.com/paularmstrong/normalizr) til at tvinge vores dybt indlejrede JSON api-svar til mere håndterbare / cacheable ID-indekserede objekter. Hvilket er at sige, vi starter med et mere traditionelt api-svar og omdanner det derefter til en tallerken, ID-indekseret enhedshash:

Som du måske kan forestille dig, er denne cache-teknik ~ super nyttig ~ når du begynder at navigere rundt i en reagerende app - for så vidt som du henter en episode, du sandsynligvis allerede har hentet en bruger (som forfatter), som du nu kan slå op med ID ved hjælp af en af ​​vælgermetoderne uden at skulle slå din backend (læs: næsten øjeblikkelig navigering. wow).

Vores schema.js er derefter, hvor vi specificerer logikken for at trække ovennævnte enhedstransformationer til vores cache (og for normalizr). Disse forholdskortlægninger ender med at være ret enkle at skrive - men bestemt let at glemme. Hvis du vil gå til redux-cache-ruten, er det def værd at se disse.

BEMÆRK SIDE Ikke afbildet ovenfor, Schema.js indeholder også en brugerdefineret mergeStrategy, som vi skrev specielt til kofangere. Uanset hvad årsagen trippede standardfletningenStrategy leveret af normalizr overalt i sig selv, men det vil jeg ikke komme ind på her, fordi det næsten helt sikkert var brugerfejl . (Når det er sagt, hvis du oplever lignende problemer, skal du lægge en note, og jeg er glad for at dele, hvor vi landede.)

Vores sidste rodfil i butikskataloget er api.js.

Efter meget at have banket hovedet, bemærkede jeg, at thunk-mellemvaren (som vi stole på til asynkiske handlinger) giver os mulighed for at videregive et ekstra argument til alle dine redux-handlinger (på toppen af ​​afsendelsen og getState).

Husk dette fra butikken / index.js

Dette er utroligt magtfuldt, og jeg endte med at bruge det til at videregive en global api-hjælper til alle vores handlinger. Denne api-hjælper (defineret i api.js) giver hurtig adgang til alle vores api-slutpunkter, med yderligere hjælpere til JSON-parsing, fejlkontrol og mere. Du kan se dette i handling nedenfor ... når vi kommer ind i ... handling ... filerne ...

Nedfotograferingsudstyr

Vores redux reducere udviklede sig til at have 3 hovedfunktioner.

  1. Definer en starttilstand
  2. Definer en forudlæstData-håndterer (for vores indlejrede data)
  3. Udsæt handlingsreduktionsreduktioner

Vores oprindelige tilstand ser ofte sådan ud med statuskonstanter til anmodningstilstand og aktive id'er:

Vores preload-behandlere tager vores rå dataobjekter og pakker dataenhederne ud, i dette tilfælde indstiller en standardaktiv bruger:

Og en typisk reducer ser sådan ud (bemærk brugen af ​​computernavnsnavne (Es2015). Vi trækker disse direkte ind fra handlingsdefinitionerne, der er dækket nedenfor).

Handlinger

Der sker nogle magiske ting i vores handlingsfiler. Først bruger vi “redux-actions” createActions-metoden til at definere vores handlingsnavne:

Vi gør dette, så vi i vores reduceringsfil kan bruge computernavnsnavne (nævnt tidligere) til kun at have vores handlingsnavne defineret et sted. Se også på den måde, vi navngiver vores handlinger på: metode + objekt + egenskab. Dette er ~ super ~ vigtigt for at holde alle dine reduceringsnøgler læsbare og unikke. Jeg har set en masse eksempler på nettet af mennesker, der bruger doven, generiske navne som "brugernavn" eller "setUsnavn" til nøgler ... tillid, du har det rigtigt dårligt, hvis du gør det (husk, nøgler er globale og fejl forårsaget af navngivning af konflikter er en stor pit for at spore).

Ved async-handlinger bruger vi redux-tunk og den api-hjælper, vi nævnte ovenfor. Dette hjælper med at holde vores asynkmetoder super stramme og fokuserede.

I eksemplet ovenfor sætter vi isFetching på brugerobjektet, affyr en anmodning til vores api, kontroller svaret for en fejlstatuskode, indstiller vores jwt-token, konverterer svaret til json, normaliserer svaret ved hjælp af normalizr (til cache) , og indstil derefter den aktive brugertilstand.

Dette er den reneste måde at håndtere async-metoder i redux, jeg nogensinde har set (ikke @ mig).

endpoints

Jeg har ikke set nogen andre lave disse endpoint-filer - men jeg synes, det er en rigtig ren måde at holde dine relevante api-opkald alle bor på ét sted (for ikke at nævne gør stubbing-tests super nemme). Bemærk også ”isomorphic-fetch” - jeg sværger en dag, at vi gengiver disse ting på serveren . I mellemtiden er den seje ting ved brug af hente, at det giver et løfte og giver en ret ren api, når den trækkes ind i vores async-handlinger.

vælgere

Til sidst bruger vores vælgers fil denormalizr-biblioteket (https://github.com/gpbl/denormalizr) (normalizrs søsterprojekt) til at genopbygge mere brugbare data fra vores cache. Det bruger grundlæggende bare navnemodellerne til at rekonstruere et stort indlejret objekt - du behøver ikke ~ at gøre dette, men jeg fandt det meget sjovere / forudsigelige at arbejde med data på denne måde.

Bortset fra det ser vores vælgermetoder stort set ud, som du ville forvente:

Konklusion

WOW. Okay, det føltes som en seriøs rejse. Og at butiksprodukter var sandsynligvis alt for kedelige og tabte som 90% af læserne, så jeg er ked af det.

Tak så meget for at have læst, og beklager, hvis dette indlæg var uudholdeligt. Jeg lovede mig selv, at jeg ville offentliggøre noget som dette, fordi jeg fandt, at det at lære alt dette lort var så sindssyge spredt / hårdt.

Hvis du har spørgsmål om noget, skal du skrive en kommentar eller en note, så gør jeg mit bedste for at besvare.

❤ fedt

Nogle Q / A

Ya, jeg er bestemt glad! Jeg ville lyve, hvis jeg ikke sagde, det var en stor pit, men Bumpers er dybest set bare en massiv lydafspiller-app - og at styre tilstand på tværs af navigationer og gennem de mange små feedbackelementer, vi har overalt, ville have været sindssygt ellers.

Jeg tror, ​​at der også er noget at sige ved brug af ”kendte” værktøjer, når du har dem - og jeg håber, hvis vi nogensinde får leje flere frontends på Bumpers, at de vil være i stand til at dykke i forholdsvis let uden at føle sig helt overvældet (og som om de har brug for at lære alt fra bunden).

Jepp, temmelig meget. Vi gjorde en lignende ting på Medium, mens jeg også var der. Du skal være forsigtig med, hvordan du gør det på grund af script-injektionshacks, men det er en temmelig sej måde at nærme sig noget som “serversides gengivelse” -følelsen uden at skulle gengive reaktionsskabeloner på serveren.