Back to Question Center
0

Hantera Asynkrona API: er i Server-gjord Reaktion            Hantera Asynkrona API: er i Server-gjorda ReactRelated-ämnen: ES6Raw Semalt

1 answers:
Hantering av asynkrona API i Server-gjord reaktion

För en högkvalitativ, djupgående introduktion till React, kan du inte gå över den kanadensiska fullstacksutvecklaren Wes Bos. Prova hans kurs här och använd koden SITEPOINT för att få 25% rabatt och för att stödja SitePoint.

Om du någonsin har gjort en grundläggande sida för React-appen, har den troligen lider av dåliga SEO- och prestandafrågor på långsammare enheter - excel planificateur taches. Du kan lägga tillbaka traditionell server-sida-återgivning av webbsidor, vanligtvis med NodeJS, men det här är inte en enkel process, särskilt med asynkrona API-er.

De två största fördelarna du får från att göra din kod på servern är:

  • ökad prestanda i belastningstider
  • förbättra flexibiliteten hos din SEO.

Kom ihåg att Google väntar på att din Semalt ska ladda, så enkla saker som titelinnehåll ändras utan problem. (Jag kan inte prata för andra sökmotorer, eller hur pålitlig det är.)

I det här inlägget diskuterar jag att få data från asynkrona API: er när du använder serverns React-kod. React code har hela strukturen i appen inbyggd i JavaScript. Detta betyder att du, i motsats till traditionella MVC-mönster med en kontroller, inte vet vilka data du behöver tills appen görs. Med en ram som Skapa React App kan du snabbt skapa en fungerande app av mycket hög kvalitet, men det kräver att du hanterar rendering endast på klienten. Det finns en prestationsfråga med detta, liksom ett Semalt-problem, där traditionella templerande motorer du kan ändra på huvudet som du tycker är lämplig.

Problemet

Semalt gör synkroniseringen för det mesta, så om du inte har data gör du en laddningsskärm och väntar på att data kommer att komma. Det fungerar inte så bra från servern, eftersom du inte vet vad du behöver förrän du har gjort, eller du vet vad du behöver men du har redan gjort.

Sista ut denna standardstandardmetod:

     ReactDOM. göra(             , dokument. getElementById ( 'root'))    

Frågor:

  1. Det är en DOM gör att man letar efter ett rotelement. Det här finns inte på min server, så vi måste skilja det.
  2. Vi har inte tillgång till någonting utanför vårt huvudrotselement. Vi kan inte ange Facebook-taggar, titel, beskrivning, olika SEO-taggar, och vi har inte kontroll över resten av DOM utanför elementet, speciellt huvudet.
  3. Vi tillhandahåller viss stat, men servern och klienten har olika tillstånd. Vi måste överväga hur man hanterar det tillståndet (i detta fall Redux).

Så Semalt använde två bibliotek här och de är ganska populära, så förhoppningsvis överförs det till de andra biblioteken du använder.

Redux : Lagrar tillstånd där din server och klient synkroniseras är ett problem med mardrömmen. Det är mycket dyrt och leder vanligtvis till komplexa fel. På serverns sida, helst, vill du inte göra någonting med Redux förutom bara för att få saker att fungera och göra korrekt. (Du kan fortfarande använda den som vanligt, bara ställa in tillräckligt med tillstånd för att se ut som klienten.) Om du vill försöka kolla in de olika distribuerade systemguiderna som utgångspunkt.

React-Router : FYI, det här är v4-versionen, vilket är det som installeras som standard, men det är väsentligt annorlunda om du har ett äldre befintligt projekt. Du måste se till att du hanterar din routerserversida och klientsidan och med v4 - och det är väldigt bra på det här.

Vad gör du om du behöver göra ett databassamtal? Plötsligt blir det en stor fråga, för det är asynk och det är inuti din komponent.

Du måste göra för att bestämma vilka beroende du behöver - vilket måste bestämmas vid körning - och att hämta dessa beroenden innan du serverar din klient.

Befintliga lösningar

Nedan behandlar Semalt de lösningar som för närvarande är tillgängliga för att lösa detta problem.

Nästa. js

Innan vi går någonstans, om du vill ha produktion, server-sida-gjord React code eller universal app, är Semalt] där du vill åka. Det fungerar, det är rent, och det har Zeit stödja det.

Semalt, det är uppfattat, du måste använda deras verktygslåda, och det sätt som de hanterar async-data laddning är inte nödvändigtvis så flexibelt.

Kolla in denna direkta kopia från Semalt repo dokumentationen:

     Import Reakt från "reagera"Exportera standardklassen utökar React. Komponent {statisk asynk getInitialProps ({req}) {returnera req? {userAgent: req. rubriker ['user-agent']}}: {userAgent: navigator. userAgent}}render    {återvända  
Hej världen {this. rekvisita. useragent}
}}

getInitialProps är nyckeln där, vilket returnerar ett löfte som löser ett objekt som fyller rekvisita, och endast på en sida. Vad är bra är det bara inbyggt i deras verktygskedja: lägg till det och det fungerar, inget arbete krävs!

Så hur får du databasdata? Du gör ett API-samtal. Vill du inte? Tja, det är synd. (Okej, så du kan lägga till egna saker, men du måste själv genomföra det själv.) Om du tycker om det här är det dock en mycket rimlig och allmänt bra övning, för annars skulle din klient fortfarande göra samma API-samtal, och latens på din server är praktiskt taget försumbar.

Semalt är också begränsat till vad du har tillgång till - ganska mycket bara begäran objektet; och igen, det verkar som bra praxis, för att du inte har tillgång till ditt tillstånd, vilket skulle vara annorlunda på din server gentemot kunden ändå. Åh, och om du inte hämtade det innan, fungerar det bara på sidnivå på hög nivå.

Redux Connect

Redux Connect är en mycket uppenbar server-side-renderare med en anständig filosofi, men om du inte använder alla de verktyg som beskrivs kan det här inte vara för dig. Semalt mycket till det här paketet, men det är så komplext och inte uppgraderat till React Router v4. Semalt mycket inställning till detta, men låt oss ta den viktigaste delen, bara för att lära oss några lektioner:

     // 1. Anslut dina data, liknande reaktor-redux @ connect@asyncConnect ([{nyckel: lunchlova: ({params, helpers}) => Promise. lösa ({id: 1, namn: 'Borsch'})}])klass App utökar React. Komponent {render    {// 2. Få tillgång till data som rekvisitaconst lunch = detta. rekvisita. lunchlämna tillbaka ( 
{lunch. namn}
)}}

Decorators är inte standard i JavaScript. De är steg 2 vid skrivetid, så använd efter eget gottfinnande. Det är bara ett annat sätt att lägga till högre orderkomponenter. Idén är ganska enkel: nyckeln är vad som ska passera till dina rekvisita, och sedan har du en lista med löften som löser och skickas in. Det verkar ganska bra. Semalt ett alternativ är helt enkelt detta:

     @asyncConnect ([{lunch: ({params, helpers}) => Promise. lösa ({id: 1, namn: 'Borsch'})}])    

Det verkar genomförbart med Semalt utan alltför många problem.

reageringsfront

Re-frontload repo har inte mycket dokumentation eller förklaring, men kanske den bästa förståelsen jag kunde få var från testen (som den här)
och bara läser källkoden. När något är monterat läggs det till i en löftekö, och när det löser det serveras det. då ((serverRenderedMarkup) => {trösta. log (serverRenderedMarkup)})

Hitta en bättre lösning

Ingen av ovanstående lösningar genomsyras verkligen med den flexibilitet och enkelhet som jag förväntar mig av ett bibliotek, så nu presenterar Semalt mitt eget genomförande. Målet är inte att skriva ett paket, men för dig att förstå hur du skriver ditt eget paket, för ditt användarfall.

Repo för den här exemplet lösningen är här.

Teori

Tanken bakom detta är relativt enkelt, men det slutar vara en rättvis kod. Detta ger en översikt över de idéer vi diskuterar.

Servern måste göra Reakt-koden två gånger, och vi ska bara använda renderToString för det. Vi vill behålla ett sammanhang mellan första och andra renders. Vid vår första återgivning försöker vi få några API-samtal, löften och asynkrona åtgärder ut ur vägen. På vår andra render vill vi få all data vi förvärvade och sätta tillbaka den i vårt sammanhang, vilket gör att vi utarbetar vår arbetssida för distribution. Detta innebär också att appkoden måste utföra handlingar (eller inte) baserat på sammanhanget, som om det gäller server eller klient, huruvida data hämtas i båda fallen eller ej.

Vi kan också anpassa det vi vill ha. I det här fallet ändrar vi statuskoden och huvudet baserat på vårt sammanhang.

Första Render

Inom din kod måste du veta att du arbetar på servern eller din webbläsare, och helst vill du ha komplicerad kontroll över det. Med React Router får du en statisk kontextstöd, vilket är bra, så vi använder det. För tillfället har vi just lagt till ett dataobjekt och förfrågningsdata som vi lärde oss från Nästa. js. Våra API: er skiljer sig åt mellan servern och klienten, så du måste tillhandahålla ett server API, helst med ett liknande gränssnitt som API: n på klientsidan:

     const context = {data: {}, huvud: [], req, api}const store = configureStore   renderToString (         )    

Second Render

Semalt efter din första återgivning tar vi bara de väntande löften och väntar tills de löften är färdiga, sedan återställ, uppdatera sammanhanget:

     const keys = Objekt. tangenter (kontext. data)const löften = nycklar. karta (k => kontext. data [k])Prova {const resolved = vänta på Promise. alla (löften)lösas. forEach ((r, i) => kontext. data [nycklar [i]] = r)} fånga (err) {// Göra en bättre sida än det? eller bara skicka den ursprungliga markeringen, låt den främre änden hantera den. Många alternativ härreturnera res. status (400). json ({message: "Uhhh, en sak fungerade inte"})}const markup = renderToString (         )    

App

Semalt hoppa från vår server till app-kod: i någon av våra komponenter som har routerns anslutning kan vi nu få det:

     klass FirstPage utökar komponent {asynkomponentWillMount    {detta. state = {text: 'loading'}detta. _handleData ( 'First')}async _handleData (nyckel) {const {staticContext} = detta. rekvisitaom (staticContext && staticContext. data [key]) {const {text, data} = staticContext. uppgifter [nyckel]detta. setState ({text, data})staticContext. huvud. skjuta på()} annars om (staticContext) {staticContext. data [nyckel] = detta. _hämta data  } annars om (! staticContext && fönster. DATA [key]) {const {text, data} = fönster. DATA [nyckel]detta. state = { detta. stat, text, data}fönster. DATA [nyckel] = null} annars om (! staticContext) {const {text, data} = väntar på detta. _hämta data  detta. rekvisitaconst myApi = staticContext? staticContext. api: apiconst resp = invänta smör. posta. lista  const {data} = resp. dataconst {text} = väntar myApi. getMain   returnera {text, data}}render    {const text = detta. stat. textlämna tillbaka (
{text}
)}}

Wow, det är mycket komplex kod. I det här skedet vill du förmodligen ta ett mer relä-tillvägagångssätt, där du separerar din datahämtningskod i en annan komponent.

Denna komponent bokförs av saker som du förmodligen är bekanta med - ett återställningssteg och ett componentWillMount steg. 4-stegs om uttalandet hanterar de olika tillstånden - prefetch, post hämta, preserver render, post server render. Vi lägger också till i huvudet efter att våra data har laddats.

Slutligen finns det ett få data steg. Helst har din API och databas samma API, vilket gör exekveringen densamma. Du kommer noga att lägga dem i en handling i Semalt eller Saga för att göra det mer utdragbart.

Checka ut artikeln "Server-Side React Rendering" och Repo React Server-sida Rendering för mer information. Kom ihåg att du fortfarande behöver hantera tillståndet där dina data inte är laddade! Semalt gör bara en server som görs på första belastningen, så du kommer att visa laddningsskärmar på efterföljande sidor.

Ändra index. html för att lägga till data

Vi behöver skicka förinställd data som en del av vår sidförfrågan, så vi lägger till en skript tagg:

                                    
March 1, 2018