Stateful vs Stateless Functional Components i React

React är ett populärt JavaScript-front-end-bibliotek för att bygga interaktiva användargränssnitt. React har en relativt grundlig inlärningskurva, vilket är en av anledningarna till att det blir all uppmärksamhet på senare tid. 

Även om det finns många viktiga begrepp som ska täckas, är komponenter otvetydigt Reacts hjärta och själ. Att ha en bra förståelse för komponenter bör göra ditt liv enkelt som en React-utvecklare. 

förutsättningar

Denna handledning är avsedd för nybörjare som har börjat lära sig React och behöver en bättre överblick över komponenterna. Vi kommer att börja med komponentbasics och sedan gå vidare till mer utmanande begrepp som komponentmönster och när man ska använda dessa mönster. Olika komponent klassificeringar har täcks, såsom klass vs funktionella komponenter, stateful vs stateless komponenter och container vs presentationsdelar. 

Innan jag börjar, vill jag presentera dig för kodkoden som vi ska använda i den här handledningen. Det är en enkel räknare byggd med React. Jag kommer att referera tillbaka till några delar av detta exempel under hela handledningen. 

Så låt oss börja. 

Vad är komponenter?

Komponenter är självbärande, oberoende mikroenheter som beskriver en del av ditt användargränssnitt. En applikations gränssnitt kan delas upp i mindre komponenter där varje komponent har sin egen kod, struktur och API. 

Facebook har till exempel tusentals funktionalitetsstycken sammankopplade när du ser deras webbapplikation. Här är ett intressant faktum: Facebook består av 30 000 komponenter, och antalet växer. Komponentarkitekturen låter dig tänka på varje bit i isolering. Varje komponent kan uppdatera allt inom sitt omfattning utan att vara oroad över hur det påverkar andra komponenter. 

Om vi ​​tar Facebook-användargränssnittet som ett exempel, skulle sökfältet vara en bra kandidat för en komponent. Facebook: s Newsfeed skulle göra en annan komponent (eller en komponent som är värd för många delkomponenter). Alla metoder och AJAX-samtal som berörs av sökfältet ligger inom den komponenten.

Komponenterna kan också återanvändas. Om du behöver samma komponent på flera ställen är det enkelt. Med hjälp av JSX-syntax kan du deklarera dina komponenter var du än vill att de ska visas, och det är det. 

 
Nuvarande antal: this.state.count
/ * Återanvändning av komponenter i åtgärd. * /

Rekvisita och stat

Komponenter behöver data att arbeta med. Det finns två olika sätt att kombinera komponenter och data: antingen som rekvisita eller stat. rekvisita och stat bestämmer vad en komponent gör och hur den beter sig. Låt oss börja med rekvisita.

rekvisita

Om komponenter var vanliga JavaScript-funktioner, skulle rekvisita vara funktionsingången. Genom att följa denna analogi, accepterar en komponent en ingång (vad vi kallar rekvisita), bearbetar den och gör sedan en del JSX-kod.

Även om data i rekvisita är tillgängliga för en komponent, är React-filosofin att rekvisita bör vara oföränderlig och upp-ner. Vad det här betyder är att en föräldrakomponent kan överföra vilken data den vill ha sina barn som rekvisita, men barnkomponenten kan inte ändra sina rekvisita. Så, om du försöker redigera rekvisita som jag gjorde nedan får du "Kan inte tilldela skrivskyddad" TypeError.

const-knapp = (rekvisita) => // rekvisita är endast läst props.count = 21; ...

stat

Stat, å andra sidan, är ett objekt som ägs av den komponent där den deklareras. Dess omfattning är begränsad till den aktuella komponenten. En komponent kan initiera sitt tillstånd och uppdatera det när det behövs. Förälderkomponentens tillstånd slutar vanligtvis att vara rekvisita för barnkomponenten. När staten har gått ut ur det nuvarande omfånget, hänvisar vi till det som en prop.


Nu när vi känner till komponentens grunder, låt oss ta en titt på den grundläggande klassificeringen av komponenter.

Klasskomponenter kontra funktionella komponenter

En Reaktorkomponent kan vara av två typer: antingen en klasskomponent eller en funktionell komponent. Skillnaden mellan de två är tydlig från deras namn. 

Funktionella komponenter

Funktionella komponenter är bara JavaScript-funktioner. De tar in en valfri ingång som, som jag har nämnt tidigare, är vad vi kallar rekvisita.


Vissa utvecklare föredrar att använda de nya ES6-pilfunktionerna för att definiera komponenter. Pilfunktionerna är mer kompakta och ger en kortfattad syntax för att skriva funktionsuttryck. Genom att använda en pilfunktion kan vi hoppa över användningen av två nyckelord, fungera och lämna tillbaka, och ett par lockiga fästen. Med den nya syntaxen kan du definiera en komponent i en enda rad så här. 

const Hello = (name) => (
Hej, name!
);

Klasskomponenter

Klasskomponenter erbjuder fler funktioner, och med fler funktioner kommer mer bagage. Den främsta orsaken att välja klasskomponenter över funktionella komponenter är att de kan ha stat.

Staten = count: 1 syntax ingår i funktionen för offentlig klassfält. Mer om detta nedan. 

Det finns två sätt att skapa en klasskomponent. Det traditionella sättet är att använda React.createClass (). ES6 introducerade ett syntaksocker som låter dig skriva klasser som sträcker sig React.Component. Båda metoderna är emellertid menade att göra samma sak. 

Klasskomponenter kan existera utan tillstånd också. Här är ett exempel på en klasskomponent som accepterar en ingående rekvisita och gör JSX.

klass Hej utökar React.Component konstruktör (rekvisita) super (rekvisita);  render () returnera ( 
Hej rekvisita
)

Vi definierar en konstruktormetod som accepterar rekvisita som input. Inuti konstruktören ringer vi super() att ge ner vad som är ärvt från förälderklassen. Här är några detaljer som du kanske har missat.

För det första är konstruktören valfri när man definierar en komponent. I det ovanstående fallet har komponenten ingen stat, och konstruktören verkar inte göra något användbart. this.props används inom göra() kommer att fungera oavsett om konstruktören är definierad eller inte. Men här är något från de officiella dokumenten:

Klasskomponenterna ska alltid ringa till baskonstruktorn med rekvisita.

Som en bra metod rekommenderar jag att du använder konstruktören för alla klasskomponenter.

För det andra, om du använder en konstruktör, måste du ringa super(). Detta är inte valfritt, och du får syntaxfelet "Saknar super () anrop konstruktör" annat. 

Och min sista punkt handlar om användningen av super() mot. super (rekvisita). super (rekvisita) ska användas om du ska ringa this.props inuti konstruktören. Annars använder du super() ensam är tillräcklig.

Stateful Components vs Stateless Components

Detta är ett annat populärt sätt att klassificera komponenter. Och kriterierna för klassificeringen är enkla: de komponenter som har tillstånd och de komponenter som inte gör det. 

Stateful Components

Statliga komponenter är alltid klasskomponenter. Som tidigare nämnts har statliga komponenter en stat som initieras i konstruktören. 

// Här är ett utdrag ur räknarexemplarkonstruktorn (rekvisita) super (rekvisita); this.state = count: 0; 

Vi har skapat ett statobjekt och initialiserat det med ett räkning på 0. Det finns en alternativ syntax föreslås för att göra det enklare att kalla klassfält. Det är inte en del av ECMAScript-specifikationen än, men om du använder en Babel-transpiler ska den här syntaxen fungera ur rutan.

klass App utökar komponent / * // Inte krävs längre constructor () super (); this.state = count: 1 * / state = count: 1; handleCount (värde) this.setState ((prevState) => (count: prevState.count + värde));  render () // utelämnat för korthet

Du kan undvika att använda konstruktorn helt och hållet med den här nya syntaxen.

Vi kan nu få tillgång till staten inom klassmetoderna inklusive göra(). Om du ska använda dem inuti göra() för att visa värdet av det aktuella räknet, måste du placera det inuti lockiga fästen enligt följande:

render () return (Nuvarande antal: this.state.count)

De detta nyckelord här hänvisar till förekomsten av den aktuella komponenten. 

Initialisering av staten är inte tillräckligt, vi måste kunna uppdatera staten för att skapa en interaktiv applikation. Om du trodde det skulle fungera, nej, det kommer det inte.

// Fel sätt handCount (värde) this.state.count = this.state.count + value; 

 Reaktorkomponenterna är utrustade med en metod som heter setState för uppdatering av tillståndet. setState accepterar ett objekt som innehåller det nya tillståndet för räkna.

// Det här fungerar handleCount (värde) this.setState (count: this.state.count + value); 

De sets () accepterar ett objekt som en ingång, och vi ökar det föregående värdet av räkning med 1, vilket fungerar som förväntat. Det finns dock en fångst. När det finns flera setState-samtal som läser ett tidigare värde av staten och skriver ett nytt värde i det, kan vi sluta med ett tävlingsförhållande. Vad det betyder är att de slutliga resultaten inte matchar de förväntade värdena.

Här är ett exempel som borde göra det klart för dig. Prova detta i codeandbox-snippet ovan.

// Vad är den förväntade produktionen? Prova det i kodens sandlåda. handleCount (värde) this.setState (count: this.state.count + 100); this.setState (count: this.state.count + value); this.setState (count: this.state.count-100); 

Vi vill att setState ska öka räkningen med 100, uppdatera sedan den med 1 och ta bort den 100 som tidigare lagts till. Om setState utför tillståndsövergången i den faktiska ordern får vi det förväntade beteendet. SetState är dock asynkron, och flera setState-samtal kan vara sammanslagna för bättre UI-upplevelse och prestanda. Så ovanstående kod ger ett beteende som skiljer sig från vad vi förväntar oss.

Därför kan du istället för att direkt skicka ett objekt passera i en uppdateringsfunktion som har signaturen:

(prevState, rekvisita) => stateChange 

prevState är en referens till föregående tillstånd och garanteras vara aktuell. rekvisita refererar till komponentens rekvisita, och vi behöver inte rekvisita för att uppdatera staten här, så vi kan ignorera det. Därför kan vi använda den för att uppdatera tillstånd och undvika tävlingsvillkoret.

// Det rätta sättet handleCount (värde) this.setState ((prevState) => count: prevState.count +1); 

De sets () rerenders komponenten, och du har en fungerande stateful komponent.

Stateless Components

Du kan använda antingen en funktion eller en klass för att skapa statslösa komponenter. Men om du inte behöver använda en livscykelkrok i dina komponenter ska du gå till statlösa funktionella komponenter. Det finns många fördelar om du väljer att använda statlösa funktionella komponenter här. de är lätta att skriva, förstå och testa, och du kan undvika detta nyckelord helt och hållet. Men från React v16 finns det inga fördelar med att använda statlösa funktionella komponenter över klasskomponenter. 

Nackdelen är att du inte kan ha livscykelhakar. Livscykelmetoden ShouldComponentUpdate () används ofta för att optimera prestanda och för att manuellt styra vad som blir återhämtat. Du kan inte använda det med funktionella komponenter än. Refs stöds inte heller.

Containerkomponenter kontra presenterande komponenter

Detta är ett annat mönster som är mycket användbart när du skriver komponenter. Fördelen med detta tillvägagångssätt är att beteendelogiken skiljs från den logiska logiken.

Presentationskomponenter

Presentationsdelar är kopplade till vyn eller hur sakerna ser ut. Dessa komponenter accepterar rekvisita från deras behållare motsvarighet och gör dem. Allt som har att göra med att beskriva användargränssnittet bör gå här. 

Presentationskomponenterna är återanvändbara och bör förbli frikopplade från beteendesskiktet. En presentationskomponent mottar data och återuppringningar uteslutande via rekvisita och när en händelse inträffar, som en knapp som trycks, utför den en återuppringning till behållarkomponenten via rekvisita för att åberopa en händelsehanteringsmetod. 

Funktionella komponenter bör vara ditt första val för att skriva presentationsdelar om inget tillstånd krävs. Om en presentationskomponent kräver en stat, bör den vara oroad över användargränssnittet och inte faktiska data. Presentationskomponenten interagerar inte med Redux-butiken eller gör API-samtal. 

Containerkomponenter

Behållarkomponenterna kommer att hantera beteendedelen. En behållarkomponent berättar den presenterande komponenten vad som ska göras med hjälp av rekvisita. Det ska inte innehålla begränsade DOM-markeringar och stilar. Om du använder Redux innehåller en behållarkomponent koden som skickar en åtgärd till en butik. Alternativt är det här platsen där du ska placera dina API-samtal och lagra resultatet i komponentens tillstånd. 

Den vanliga strukturen är att det finns en behållarkomponent överst som överför data till sina barnpresentationer som rekvisita. Detta fungerar för mindre projekt; Men när projektet blir större och du har många mellanliggande komponenter som bara tar emot rekvisita och skickar dem vidare till barnkomponenter, blir det otäckt och svårt att underhålla. När det händer är det bättre att skapa en behållarkomponent som är unik för bladkomponenten, och detta kommer att underlätta belastningen på de mellanliggande komponenterna.

Så vad är en ren komponent?

Du kommer att få höra termen ren komponent mycket ofta i React circles, och då finns det React.PureComponent. När du är ny för att reagera kan det här låta lite förvirrande. En komponent sägs vara ren om det är garanterat att returnera samma resultat med tanke på samma rekvisita och tillstånd. En funktionell komponent är ett bra exempel på en ren komponent eftersom du vet vad som kommer att göras med en ingång. 

const HelloWorld = (name) => ( 
'Hej $ namn'
);

Klasskomponenter kan vara rena så länge deras rekvisita och tillstånd är oföränderliga. Om du har en komponent med en "djup" oföränderlig uppsättning rekvisita och tillstånd, har React API något som heter PureComponent. React.PureComponent liknar React.Component, men det implementerar ShouldComponentUpdate () Metoden lite annorlunda. ShouldComponentUpdate () åberopas innan någonting är rerdered. Standardbeteendet är att det returnerar sant så att varje förändring till staten eller rekvisita beror på komponenten.

shouldComponentUpdate (nextProps, nextState) return true; 

Men med PureComponent utför den en grund jämförelse av objekt. Grunt jämförelse innebär att du jämför objektens omedelbara innehåll istället för att rekursivt jämföra alla objektets nyckel / värdepar. Så endast objektreferenserna jämförs, och om staten / rekvisita muteras kan det här inte fungera som avsett. 

React.PureComponent används för att optimera prestanda och det finns ingen anledning till varför du bör överväga att använda den om du inte stöter på någon form av prestandafråga. 

Slutgiltiga tankar

Stateless funktionella komponenter är mer eleganta och är vanligtvis ett bra val för att bygga presentationsdelarna. Eftersom de bara fungerar, kommer du inte ha svårt att skriva och förstå dem, och dessutom är de döda lätta att testa. 

Det bör noteras att statslösa funktionella komponenter inte har överhand i form av optimering och prestanda eftersom de inte har en ShouldComponentUpdate () krok. Det kan komma att ändras i framtida versioner av React, där funktionella komponenter kan optimeras för bättre prestanda. Om du inte är kritisk mot prestanda, bör du dock hålla fast vid funktionella komponenter för visning / presentation och stateful klasskomponenter för behållaren.

Förhoppningsvis har denna handledning gett dig en överblick över komponentbaserad arkitektur och olika komponentmönster i React. Vad är dina tankar om detta? Dela dem genom kommentarerna.

Under de senaste åren har React vuxit i popularitet. Faktum är att vi har ett antal objekt i Envato Market som är tillgängliga för köp, granskning, genomförande och så vidare. Om du letar efter ytterligare resurser runt React, tveka inte att kolla in dem.