Is niet onze code alleen de * BESTE *

Uitzicht vanaf de 6 weken in de hel besteedde ik aan het herschrijven van bumpers in reactie.

Ik heb de Bumpers-web-app volledig herschreven met behulp van reageren. (Als je niet weet wat bumpers is, is het deze superchillige app voor het opnemen / delen van audioverhalen op je telefoon. Download het, het zal je hele leven lang kloppen. Het is de beste app ooit gemaakt. Reageren? Het is aight.)

Hoe dan ook, wat volgt zijn al mijn aantekeningen, gedachten, enz. Over dit proces. (Dingen die ik wou dat ik had gelezen voordat ik begon). Hopelijk haal je er iets uit.

Voorwoord

GOD. Ik haat kaders. Veel.

Ik haat ook het ontbreken van een framework en iedereen die zijn eigen 'framework' rolt. Ik heb ook gewoon een hekel aan codering, in het algemeen. En vooral haat ik het schrijven over code.

Dus verdraag me.

De laatste tijd is mijn codeerstijl een soort van sociopatische, oscillatie tussen aanvallen van verlammende zelftwijfel en een extreem kanye-achtig godcomplex - waar ik ofwel de hele dag alleen rond mijn appartement marcheer of hardop roep om ze weet dat haar 30-jarige zoon "het spel aan het neuken is (op een goede manier)". Dus leek het natuurlijk een goed moment om een ​​pauze te nemen en erover te schrijven.

(moraal tijdens een project lifecyle, rode noten mijn) - https://medium.com/@iano/moral-over-a-project-lifecycle-975792b54c12#.uwkzt7x4v)

Reageren

Een beetje geschiedenis: de "website" van de bumpers was zo leuk. Er waren ongeveer 7 es6-klassen, geen externe afhankelijkheden en slechts ongeveer 759 coderegels. Totaal.

De lay-outs werden op de server weergegeven door onze Go-app. We hebben postcss gebruikt. We hadden een heel eenvoudige activamap waarin we al onze svgs plaatsten, en een video of twee. Het was geweldig. We hebben wat javascript geschreven. We zijn het vergeten.

Het was geweldig. We hebben wat javascript geschreven. We zijn het vergeten.

Ondertussen maakte Nicolas Gallagher deel uit van het team dat net een project van een jaar had voltooid om het mobiele webproduct van Twitter in React te herschrijven.

Ik ken Nicolas al heel lang. En hij is gemakkelijk een van de meer attente mensen die ik ken. Dus toen hij me daarna vertelde dat React in wezen alle problemen in de Front End Development-ruimte had opgelost en dat hij zich verder ging bezighouden met andere dingen, zei ik hem meteen te stoppen.

Op het eerste gezicht had React deze dingen goed:

  • onderschreven door vrienden die slimmer zijn dan ik zoals Nicolas Gallagher, Alex MacCaw, Guillermo Rauch
  • client-side rendering (goed voor audio-apps, zodat u kunt blijven afspelen tijdens navigatie)
  • doordacht componentmodel
  • mensen verhuisden weg van (of op zijn minst uitdagend) CSS
  • facebook nerds schreef het
  • productie-apps ~ zoals Instagram, Twitter, enz. ~ gebruikten het
  • mensen leken eindelijk een dataparadigma in redux te regelen (en het leuk te vinden)

Maar tegelijkertijd had React een aantal dingen waar ik niet enthousiast over was:

  • Mijn 700-regel javascript-bundel stond op het punt om ~ 1.5mb te worden
  • productie server-side rendering vereist een knooppuntserver (en zelfs toen leken oplossingen half gebakken)
  • stijlpraktijken zijn super gefragmenteerd in de hele gemeenschap (gebruik je aphrodite, css-modules, stijltags, etc. - hoe zit het met je afhankelijkheden?)
  • facebook nerds schreef het
  • webpack → babel → jsx → hot loading → bronkaarten → chrome tools als een stapel mijn arme kleine macbook verlamde
  • Ik moest deze verdomde "egghead" -video's bekijken om redux te leren
  • tooling leek onsamenhangend en overdreven ...

Ondanks dit alles besloten we ervoor te gaan. (De belangrijkste hoop om te reageren zou ons op de een of andere manier iets laten bouwen dat meer "app-y" aanvoelde).

"De rest" kiezen

Het blijkt dat nadat je hebt besloten om te reageren (je view bibliotheek), je echt nog een handvol andere beslissingen hebt: hoe ga je de staat beheren? Hoe ga je je componenten stylen? Ga je es6 gebruiken? ES7? es2015? JSX? Wat betekenen die eigenlijk? Gaat u webpack gebruiken? of browsen? Waar gaat alles leven? ...

Ik begon met het samenvoegen van TJ Holowaychuk's boilerplate-repo (https://github.com/tj/frontend-boilerplate/tree/master/client) (die hij toegeeft dat hij in feite verouderd is in de readme) en deze lange e-mail die Nicolas had geschreven mij over waar twitter was geland (waarvan ik de helft toen nog niet begreep, maar hoe dan ook, je kunt de e-mail hier in zijn geheel lezen: https://gist.github.com/fat/9ab5325ab39acfe242bc7849eb9512c4).

Ik heb ook gekeken naar een paar van de vele "universal-react-redux-glahblbhalkd" boilerplate-repo's op github, maar ze gaven me grotendeels allemaal paniekaanvallen.

Hoe dan ook, ik ben er op de een of andere manier in geslaagd om een ​​plaats te bereiken waar ik enigszins gelukkig mee ben, die eruit ziet als:

  • Babel (met “presets”: [“es2015”, “stage-0”, “react”]) Dus ik kan alle gekke nieuwe shit zoals spread-operators, pijlfuncties, etc. gebruiken
  • Webpack met hotloaders, wat ik (toen het werkte) handig vond bij het bijwerken van de stijl bij bepaalde app-statussen. Maar zorgde zeker voor veel angst. Eerlijk gezegd heb ik het gevoel dat niemand echt begrijpt hoe webpack werkt. En we blijven allemaal willekeurige eigenschappen en plug-ins gooien en bidden dat het allemaal zal lukken. AggressiveMergingPlugin? zeker. OccurrenceOrderPlugin? OK. DedupePlugin? prima.
  • Redux in combinatie met normilzr en denormalizr om te helpen bij het vernietigen en vervolgens rehydrateren van api-reacties.
  • Aphrodite / no-important js-stijlen, geen css, maar zonder al die! Overal belangrijk.
  • Svg-react-loader die svg's laadt als inline componenten.
  • Een handvol anderen als je iets anders ziet in die afhankelijkheidslijst waar je nieuwsgierig naar bent, laat een briefje achter en ik leg het uit.

Directorystructuur

OKE. Toen ik eenmaal de 38 afhankelijkheden-bumpers had vastgesteld. Als de website niet nodig was, was het tijd om wat echte code te schrijven.

Onze directorystructuur is georganiseerd rond twee toegangspunten:

  • index.js die de router instantieert en opslaat voor onze kernapp-bundel.
  • embed.js die verantwoordelijk is voor onze kleinere embed-bundel (zoals te zien in slack, twitter, medium, etc).

Van daaruit halen we onze routes uit de toepasselijk genaamde "route" -map, die op dit moment gewoon een eenvoudige, enkele router-component is die er zo uitziet:

Merk op dat deze routes verwijzen naar wat we schermcontainers call noemen.

In Bumpers zijn onze reactcomponenten eigenlijk opgesplitst in 3 verschillende mappen, afhankelijk van hun functie (4 als u de routemap opneemt). Deze manier om componenten te organiseren, is in feite gewoon rechtstreeks van Twitter gestolen, die ik op zijn beurt volgens mij van Facebook en tal van andere projecten heeft geleend. Het lijkt op:

  • componenten hier wonen onze functionele ui-componenten
  • containers hier wonen actiehandlers voor onze ui-componenten
  • schermen technisch gezien zijn dit slechts containers - maar meestal doen ze meer pagina's op het hoogste niveau en zijn ze minder bezig met het afhandelen van acties.
BIJZONDERE Ik begon eigenlijk met alleen een map met containers, geen "schermen" (wat vrij gebruikelijk is van wat ik heb gezien in de reactcommunity). Ik ben hiernaartoe gegaan op advies van Nicolas en omdat het zien van een aantal "scherm" achtervoegsel-bestanden gemengd met mijn niet-"scherm" achtervoegsel-bestanden me dwars zat.

De laatste twee mappen zijn de map "store" en de map "constants". De "store" bevat al onze redux-logica, zoals acties, reducers, selectors, api-eindpunten, enz. (Waar ik hieronder meer in detail op inga), terwijl de directory "constanten" ... wel ... constanten bevat.

UI-componenten

Onze UI-componenten zijn vrij standaard, functionele, stateloze, presentatie-, reactieve componenten. Hier is een standaard afleveringscomponent (die bestaat uit vele andere kleinere, standaard, functionele, staatloze, presentatie-, reactieve componenten).

Zoals ik hierboven al zei, gebruiken we Aphrodite van Khan Academy om onze CSS te genereren.

SNELLE OPMERKING Oorspronkelijk schreef ik de app met het style-loader-pakket, maar het onvermogen om een ​​overtuigende serverstrategie te bieden (iets dat ik uiteindelijk wil verkennen), was genoeg voor mij om iets anders te proberen. (Ik dacht ook routinematig aan React-Native, waaraan Nicolas me constant zou herinneren dat het beter was dan de oplossing die ik onafhankelijk had gevonden omdat hij het had geschreven).

Desondanks kwam het schrijven van mijn stijlen in javascript vrij natuurlijk, en met behulp van nieuwe ES6-functies kon het redelijk elegant worden gemaakt.

Ik was in staat om een ​​vergelijkbare stijl te bereiken als wat we deden toen ik bij Medium werkte, typeschalen, kleurenschalen, zIndex-schalen, enz. Creëerde en was zelfs in staat om de ES6 berekende eigenschapnamenfunctie te gebruiken om mijn mediaquery's in variabelen samen te vatten .

Een ding waar ik niet op kon ingaan, was om al mijn klassennamen generiek te benoemen, zoals "box" of "container" of "main" of "root". Ik krijg de hele lokale css-meme - maar het lijkt te gaan met de prijs van debugability. In plaats daarvan kwam ik eigenlijk terecht op een naamgevingssemantisch niet ver van wat werd beschreven in SuitCSS, slechts enigszins aangepast voor javascript (met "_" in plaats van "-"). In de praktijk zag dit er ongeveer zo uit:

Een laatste ding dat ik snel zal noemen, zijn al onze relevante bestanden live in hun respectieve componentmappen.

Stijlen worden in een apart bestand met de naam style.js geplaatst, naast relevante svg-middelen die rechtstreeks worden geïmporteerd met svg-react-loader. Hierdoor is het super eenvoudig om componenten / functies te verwijderen en niet constant aan jezelf te blijven vragen: wacht, heb ik deze css nog nodig? heb ik deze svg nog nodig?

Containers pauze

Eerlijk gezegd ga ik niet veel zeggen over containers ™. We doen hier niets bijzonders, behalve het scheiden van de mappen voor schermen / containers (die ik hierboven al heb behandeld).

Ik heb echter nog een foto voor je gemaakt (wauw, daar) omdat ik me slecht voelde omdat ik niet veel te zeggen had over containers . En ik dacht dat dit een goed moment voor je was om een ​​pauze te nemen misschien. Rekken?

Sorry.

Winkel

~ ALRIGHT ~. Dit winkelgedeelte kan gemakkelijk het EIGEN VOLLEDIG ARTIKEL zijn, maar ik ga proberen er voor jullie allemaal doorheen te sjouwen ... dus houd er rekening mee. Ook eerlijke waarschuwing - het gaat DENSE krijgen.

ZIJ OPMERKING: wat volgt is waarschijnlijk helemaal nergens op slaan tenzij je bekend bent met redux (http://redux.js.org/). Als je meer wilt weten over Redux en het wilt gebruiken om de status in je React-apps te beheren, raad ik je aan deze egghead-tutorials te bekijken, ze zijn gratis en allemaal redelijk goed: https://egghead.io/courses/getting -gestarte-met-redux

Onze winkel bestaat uit 4 bestanden op het hoogste niveau (ik ga hieronder in meer detail in, maar gewoon heel snel) ...

  • index.js - onze winkel-initializer
  • reducer.js - trekt alle reducers van verschillende objecten in één gigantische "combinReducers" -methode
  • schema.js - al onze normalizr-modellen
  • api.js - een api-helper voor onze winkel

Bovendien is onze winkel gestructureerd rond modellen, met mappen zoals gebruikers, prompts, etc. - in plaats van de traditionele functionele directory-hiërarchie van redux op het hoogste niveau van acties /, reducers /, selectors /, bleh.

Natuurlijk hebben we nog steeds de traditionele scheiding van acties, reductiemiddelen enz. Die redux vereist - maar dit gebeurt nu op bestandsniveau, genest in de modeldirectory (kijk naar de uitgebreide gebruikersmap in de afbeelding links voor een illustratie van wat ik probeer te zeggen).

OK, maar waarom dan? Bij het bouwen van deze app merkte ik dat ik constant dingen zei als: "dang, ik wil alleen maar werken aan user stuff rn" en bijna nooit iets zeggen als: "dang, ik wil gewoon een aantal reductiemiddelen allemaal tegelijk veranderen, ben zeker blij ze bevinden zich allemaal in deze gigantische verdomde reductiemap ”.

BIJZIJDE Ik kan me niet herinneren waar ik deze strategie voor het eerst zag ... maar ik heb er vertrouwen in dat ik hem niet heb uitgevonden. Als je iemand kent die dat wel heeft gedaan, of die het goed uitlegt, laat dan een briefje achter en ik wil je daar graag op wijzen. Ook denk ik ~ Twitter doet iets soortgelijks. Maar dat zou ik kunnen verzinnen.

Nitty gritty van de root level bestanden

Oké, dus de index.js van de winkel (hierboven kort genoemd) is verantwoordelijk voor 3 hoofdtaken:

  1. Pre-fetching, embedded data in onze redux store importeren en de initiële status van de store instellen (onze backend haalt gegevens op wanneer een gebruiker toegang heeft tot iets als bumpers.fm/fat zodat wanneer de react-app wordt geladen, deze niet onmiddellijk een xhr-verzoek om gebruikersgegevens, en in plaats daarvan kan de pagina snel worden ingevuld).
  2. initialiseren van onze redux-winkel met onze rootreductoren.
  3. middleware zoals thunk toepassen, de browsergeschiedenis van de router, devtools en meer ...

In de praktijk zag dit er allemaal ongeveer zo uit als de onderstaande methode - maar om welke reden dan ook veroorzaakte het me veel verdriet:

Laten we vervolgens kort ons reducers.js-bestand bezoeken, wat in wezen slechts een enkele combineerreductiemethode is die reductiemiddelen uit onze andere mappen haalt en ze blootstelt als een gigantisch reducerend waterval-dingetje. tbqh, dit bestand is behoorlijk saai en ik had het waarschijnlijk gewoon in index.js kunnen zetten. whoops.

Echter! Een ding dat de moeite waard is om hier te noemen, is dat onze "entiteiten" -reductor (zie hierboven) werkt als de cache van onze winkel.

Om dit voor elkaar te krijgen gebruikten we een project met de naam normalizr (https://github.com/paularmstrong/normalizr) om onze diep geneste JSON api-antwoorden te dwingen tot beter beheersbare / cacheerbare ID-geïndexeerde objecten. Dat wil zeggen, we beginnen met een meer traditionele API-reactie en transformeren deze vervolgens in een meer saaie, ID-geïndexeerde entiteitshash:

Zoals je je misschien kunt voorstellen, is deze cachetechniek ~ super handig ~ als je begint te navigeren in een react-app - in zoverre dat als je een aflevering ophaalt, je waarschijnlijk al een gebruiker (als auteur) hebt opgehaald, die je nu op ID kunt opzoeken met behulp van een van de selectiemethoden, zonder je backend te hoeven raken (lees: vrijwel onmiddellijke navigatie. wauw).

In ons schema.js specificeren we vervolgens de logica voor het verwijderen van de bovenstaande entiteitstransformaties voor onze cache (en voor normalizr). Deze relatietoewijzingen zijn uiteindelijk vrij eenvoudig te schrijven - maar zeker gemakkelijk te vergeten. Als je de redux cache route gaat volgen, is het zeker de moeite waard om deze een kijkje te geven.

SIDE NOTE Niet hierboven afgebeeld, bevat Schema.js ook een aangepaste mergeStrategy die we speciaal voor bumpers schreven. Om welke reden dan ook, de standaard mergeStrategy van normalizr struikelde over zichzelf, maar ik ga hier niet op in omdat het vrijwel zeker een gebruikersfout was . (Dat gezegd hebbende, als je soortgelijke problemen ondervindt, laat dan een briefje achter en ik ben blij om te delen waar we zijn beland.)

Ons laatste rootbestand in de store-directory is api.js.

Na veel van mijn hoofd te hebben geslagen, merkte ik dat de thunk middleware (waarop we vertrouwden voor async-acties) ons in staat stelt een extra argument door te geven aan al je redux-acties (bovenop verzending en getState).

Onthoud dit in de store / index.js

Dit is ongelooflijk krachtig en ik heb het uiteindelijk gebruikt om een ​​wereldwijde api-helper in al onze acties door te geven. Deze api-helper (gedefinieerd in api.js) biedt snelle toegang tot al onze api-eindpunten, met extra helpers voor JSON parsing, foutcontrole en meer. Je zult dit hieronder in actie zien ... wanneer we in de ... actie ... bestanden komen ...

verloopstukken

Onze redux-verloopstukken zijn geëvolueerd met 3 hoofdfuncties.

  1. Definieer een begintoestand
  2. Definieer een preloadData-handler (voor onze embedded data)
  3. Stel reductiemiddelen voor handlers bloot

Onze initiële status ziet er vaak zo uit, met statusconstanten voor aanvraagstatus en actieve id's:

Onze vooraf geladen handlers nemen onze onbewerkte gegevensobjecten en pakken de gegevensentiteiten uit, in dit geval een standaard actieve gebruiker instellen:

En een typisch verloopstuk ziet er ongeveer zo uit (let op het gebruik van berekende eigenschapsnamen (Es2015). We halen deze rechtstreeks uit de actiedefinities, die hieronder worden behandeld).

acties

Er gebeuren enkele magische dingen in onze actiebestanden. Eerst gebruiken we de methode "redux-actions" createActions om onze actienamen te definiëren:

We doen dit zodat we in ons reductiebestand de berekende eigenschapsnamen (eerder genoemd) kunnen gebruiken om onze actienamen slechts op één plaats te definiëren. Bekijk ook de manier waarop we onze acties een naam geven: methode + object + eigenschap. Dit is ~ super ~ belangrijk om al uw reduceertoetsen leesbaar en uniek te houden. Ik heb veel voorbeelden op internet gezien van mensen die luie, generieke namen gebruiken zoals "gebruikersnaam" of "setUsername" voor sleutels ... vertrouw, je gaat een echt slechte tijd hebben als je dat doet (onthoud, sleutels zijn global en bugs veroorzaakt door naamgevingsconflicten zijn een belangrijke pita om op te sporen).

Voor async-acties gebruiken we redux thunk en de api-helper die we hierboven hebben genoemd. Dit helpt onze async-methoden super strak en gefocust te houden.

In het bovenstaande voorbeeld stellen we isFetching in op het gebruikersobject, vuren een verzoek af naar onze API, controleren de respons op een foutstatuscode, stellen ons jwt-token in, converteren de respons naar json, normaliseren de respons met normalizr (voor caching) en stel vervolgens de actieve gebruikersstatus in.

Dit is de schoonste manier om async-methoden in redux te hanteren die ik ooit heb gezien (niet @ me).

Eindpunten

Ik heb nog niemand anders deze eindpuntbestanden zien doen, maar ik vind het een heel schone manier om je relevante API-oproepen allemaal op één plek te houden (om nog maar te zwijgen over het maken van stoptesten supergemakkelijk). Let ook op de "isomorphic-fetch" - ik zweer het op een dag dat we dit soort dingen op de server weergeven . Ondertussen is het coole aan het gebruik van fetch dat het een belofte oplevert en zorgt voor een vrij schone api wanneer het wordt betrokken bij onze async-acties.

selectors

Ten slotte gebruikt ons selectors-bestand de denormalizr-bibliotheek (https://github.com/gpbl/denormalizr) (het zusterproject van normalizr) om meer werkbare gegevens uit onze cache opnieuw op te bouwen. Het gebruikt in feite alleen de naammodellen om een ​​groot genest object te reconstrueren - je hoeft dit niet ~ te doen, maar ik vond het veel leuker / voorspelbaar om op deze manier met gegevens te werken.

Anders dan dat, zien onze selectiemethoden er ongeveer zo uit als je zou verwachten:

Conclusie

WAUW. Oké, dat voelde als een serieuze reis. En dat winkelmateriaal was waarschijnlijk veel te saai en verloren als 90% van de lezers, dus het spijt me.

Heel erg bedankt voor het lezen, en sorry als dit bericht onuitstaanbaar was. Ik heb mezelf net beloofd dat ik zoiets zou publiceren omdat ik vond dat het leren van al deze shit zo waanzinnig verspreid / moeilijk was.

Als je ergens vragen over hebt, laat dan een opmerking of opmerking achter en ik zal mijn best doen om deze te beantwoorden.

❤ vet

ENKELE V / A

Ja, ik ben absoluut blij! Ik zou liegen als ik niet zou zeggen dat het een grote pitabroodje was, maar Bumpers is eigenlijk gewoon een enorme app voor audiospelers - en het beheren van de status over navigaties en door de vele kleine feedbackelementen die we overal hebben, zou anders waanzinnig moeilijk zijn geweest.

Ik denk dat er ook iets te zeggen valt over het gebruik van "vertrouwde" tools als je ze hebt - en ik hoop dat als we ooit meer frontend's bij Bumpers kunnen inhuren, ze vrij gemakkelijk kunnen duiken zonder zich volledig te voelen overweldigd (en alsof ze alles vanaf nul moeten leren).

Jazeker. We deden hetzelfde bij Medium terwijl ik daar ook was. Je moet voorzichtig zijn met hoe je het doet, vanwege scriptinjectie-hacks, maar het is een vrij coole manier om zoiets als het "server side rendering" -gevoel te benaderen, zonder react-sjablonen op de server te hoeven weergeven.