Hvis du arbejder med React dagligt, vil du før eller siden opdage, at At mestre useState og useEffect gør hele forskellen mellem en komponent, der "fungerer intermitterende", og en solid, vedligeholdelsesvenlig og let-at-fejlrette brugerflade. Begge hooks virker simple i starten, men så snart du begynder at lave HTTP-anmodninger, abonnementer eller timere, opstår der tvivl, effekter kører i løkker, og tilstande bliver "forældede".
I de følgende linjer vil vi se Sådan bruger du useState og useEffect korrekt i React, fra det grundlæggende til avancerede mønstreDette kursus integrerer bedste praksis for ydeevne, afhængighedsstyring, effektoprydning, brug med eksterne API'er og nogle tricks til at undgå almindelige fejl. Målet er, at du til sidst vil være i stand til at læse enhver komponent med hooks og forstå, hvad der sker, og frem for alt, at du vil være i stand til at designe din egen uden frygt for at ødelægge noget.
Hvad er useState og useEffect præcist, og hvornår skal du bruge dem?
React introducerede Hooks fra og med version 16.8 At administrere tilstands- og livscykluslogik inden for funktionelle komponenter uden brug af klasser. Målet er at gøre det muligt for dig at komponere genanvendelig logik deklarativt ved at udnytte selve JavaScript-sproget (lukninger, rene funktioner osv.).
Krogen useState Det bruges til at tilføje lokal tilstand til en funktionel komponentI stedet for at have denne.stat y denne.sætStatDu erklærer et par [valor, setValor] React vil associere rækkefølgen af dine hook-kald i den komponent med den pågældende tilstand. Hver gengivelse vil have sin egen "version" af den pågældende tilstand.
For sin del, useEffect er hooken designet til bivirkninger.: kode, der udføres efter React har malet i DOM'en og som interagerer med eksterne systemer: dataanmodninger, websocket-abonnementer, adgang til localStorage, vinduesmanipulation, timere med setInterval o setTimeoutOsv
Selvom det ofte siges, at useEffect svarer til componentDidMount, componentDidUpdate og componentWillUnmount kombineret.Det er mere præcist at tro, at det beskriver "Hvad skal synkroniseres med det ydre efter hver rendering"Derefter definerer afhængighedsarrayet når Denne synkronisering er udført.

useState: administrer lokal tilstand deklarativt
Krogen useState Den erklæres øverst i komponenten og modtager en startværdiDen returnerer et array med to positioner: den aktuelle værdi og en funktion til at opdatere den. Noget så simpelt som en tæller er defineret sådan her:
Grundlæggende eksempel på useState:
import { useState } from "react";
funktion GrundlæggendeTæller() {
const [count, setCount] = useState(0);
konstant stigning = () => {
sætTal(tæller + 1);
};
Vend tilbage (
Du har trykket på knappen {count} gange
Tryk mig
);
}
I hver gengivelse, antal repræsenterer den tilstand, der svarer til den specifikke gengivelseNår du ringer setCountReact planlægger en ny gengivelse med den opdaterede værdi. Hvis opdateringen afhænger af den tidligere værdi, foretrækkes det at bruge funktionstilgangen:
setCount(prev => prev + 1);
Denne syntaks undgår problemer, når flere opdateringer er kædet sammen, fordi React garanterer, at prev er den sidste stabile værdiselv i samtidig tilstand.
useEffect: Forståelse af dens livscyklus og række af afhængigheder
Effektkrogens signatur er meget enkel: useEffect(efecto, dependencias?)Det første argument er en opsætningsfunktion, som kører efter DOM'en er malet. Funktionen kan eventuelt returnere en anden oprydningsfunktion, som React kalder, før effekten køres igen, eller når komponenten afmonteres.
Nøglen til effektiv brug af useEffect er at forstå rækken af afhængigheder.Dette array angiver hvilke "reaktive" værdier effektens logik afhænger af: props, tilstande og variabler eller funktioner defineret i komponenten. React sammenligner de nuværende afhængigheder med dem fra den forrige gengivelse ved hjælp af Object.isHvis der sker ændringer, skal du først køre den forrige oprydning og derefter konfigurere den nye effekt.
Afhængigt af hvordan du skriver det array, får du forskellige opførsler:
- Intet andet argumentEffekten udføres efter hver gengivelse.
- Tomt array []Effekten kører automatisk én gang efter den første gengivelse (tilstanden “componentDidMount” og “componentWillUnmount”).
- Array med afhængigheder [a, b]Effekten udføres efter den første gengivelse, og hver gang en af disse afhængigheder ændres.
Et meget typisk eksempel: Opdater dokumenttitlen med en tællerDet er den moderne funktionelle ækvivalent til det klassiske klassemønster med componentDidMount + componentDidUpdate.
Eksempel på useEffect-synkronisering af dokumenttitlen:
import { useState, useEffect } from "react";
funktion TællerMedTitel() {
const [count, setCount] = useState(0);
brugEffekt(() => {
document.title = `Du trykkede på ${count} knappen gange`;
}, [antal]);
Vend tilbage (
Knappen er blevet trykket {count} gange
setCount(tælling + 1)}>Skub mig
);
}
I dette tilfælde afhængighed er countog det skal være i arrayet, uanset hvad.fordi det bruges inden for effekten. Hvis du prøvede at udelade det, eslint-plugin-react-hooks Det ville advare dig; og hvis du tvang linteren til at holde kæft, ville du ende med svære at opdage fejl, hvor titlen ikke svarer til tællerens faktiske værdi.

Ikke-rensende effekter: logik, der kun skal udføres efter gengivelse
Der er en kategori af effekter, der de kræver ikke eksplicit rengøringDet er disse, hvor "Du gør noget, og så glemmer du det"log via konsol, rediger dokumenttitel, foretag en engangs HTTP-anmodning, mål noget kun én gang osv.
I de sociale klassers verden var denne logik fordelt blandt componentDidMount y componentDidUpdate, ofte med duplikeret kode. Med hooks forsvinder den duplikering fordi En enkelt useEffect kan håndtere både den indledende gengivelse og opdateringer..
Simpelt eksempel på en effekt uden sanitet: Hent brugerens placering ved hjælp af browserens geolocation API og gem det i staten:
import { useState, useEffect } from "react";
funktion Brugerplacering() {
const [latitude, setLatitude] = useState(0);
const [længde, sætLængde] = brugStatus(0);
brugEffekt(() => {
hvis (!navigator.geolocation) {
console.log("Browseren understøtter ikke geoplacering");
vende tilbage;
}
navigator.geolocation.getAktuelPosition((pos) => {
sætLatitude(pos.koordinater.latitude.tilFast(0));
sætLængde(pos.koordinater.længdegrad.tilFast(0));
});
}, []);
I dette tilfælde Vi ønsker kun at anmode om placeringen én gang, når komponenten er monteret.Derfor er afhængighedsmatrixen tom: der er intet, der burde udløse effekten igen. Der er ikke behov for oprydning, da vi ikke har efterladt nogen aktive intervaller, abonnementer eller lyttere.
Et andet almindeligt eksempel på en effekt uden afhjælpning er en log eller analysetracker, når bestemte oplysninger ændresFor eksempel at registrere hvilket chatrum brugeren er i, eller hvor mange varer de har i deres indkøbskurv, så længe der ikke opretholdes en liveforbindelse i forbindelse med den pågældende begivenhed.
Effekter af sanering: abonnementer, intervaller og hukommelseslækager
Den anden store kategori er effekter, der skal rengøresDet sker normalt, når man skaber noget, der burde afbryd eller annuller når afhængigheder ændres, eller komponenten afmonteres: API-abonnementer, globale eventlyttere, timere, websockets osv.
Mønsteret er altid det samme: I effekten initialiserer du ressourcen og returnerer en funktion, der fortryder den.React vil kalde den oprydningsfunktion, før effekten køres igen, og når komponenten forsvinder fra DOM'en.
Et klassisk eksempel: en tæller, der automatisk øges med intervaller.
import { useState, useEffect } from "react";
const AutomatiskTæller = () => {
const [tæller, sætTæller] = brugStatus(0);
brugEffekt(() => {
const interval = sætInterval(() => {
konsol.log("Interval...");
sætTæller((c) => c + 1);
}, 2000);
returner() => {
console.log("Rydningsinterval");
clearInterval(interval);
};
}, []);
tilbagevenden {tæller} ;
};
Her oprettes intervallet, når komponenten monteres, og slettes, når den afmonteres. Hvis du glemmer at kalde clearInterval, Intervallet ville fortsætte med at køre, selvom komponenten ikke længere blev vist.forårsager hukommelseslækager og tilstandsopdateringer på adskilte komponenter.
Et andet meget almindeligt mønster er abonnement på globale begivenhederSom resize de window o keydown en document:
import { useEffect } from "react";
funktion GlobaleBegivenheder() {
brugEffekt(() => {
const håndtagÆndringsstørrelse = () => {
console.log("Vindue ændret størrelse");
};
const handleKeyDown = (begivenhed) => {
console.log(«Taste trykket», event.tast);
};
window.addEventListener("ændre størrelse", handleResize);
dokument.addEventListener("tastned", håndtagTastNed);
returner() => {
window.removeEventListener("ændre størrelse", handleResize);
dokument.fjernEventListener("tastned", håndtagTastNed);
};
}, []);
tilbagevenden Lytter til globale begivenheder ;
}
La Rengøringen efterlader systemet præcis som det var før komponenten blev installeretDenne spejling mellem opsætning og oprydning er fundamental, især i betragtning af at React i streng udviklingstilstand kan køre en ekstra cyklus med opsætning → oprydning → opsætning for at opdage fejl.
HTTP-anmodninger med useEffect og useState: en trin-for-trin opskrift
En af de mest almindelige anvendelser af useEffekt es indlæse data fra en API, når komponenten er monteret og gemme resultatet i en tilstand med useStateDer er en meget praktisk "opskrift", som du kan genbruge næsten hver gang:
- Komponenten starter i "indlæsningstilstand" med en typetilstand
isLoading. - Foretag anmodningen inde i useEffect lige efter React maler komponenten.
- Gem svaret i staten og mærke
isLoadingsomfalsenår jeg er færdig. - Gengiv betinget en spinner eller indlæsningsbesked under
isLoadinghavettrueog dataene, når de er klar.
Komplet eksempel: App, der viser en tilfældig hvalp ved hjælp af Dog API'en.
import { useState, useEffect } from "react";
funktion TilfældigHund() {
const [imageUrl, setImageUrl] = useState(null);
const [isLoading, setIsLoading] = useState(sand);
const [fejl, sætFejl] = brugStatus(null);
brugEffekt(() => {
const hentHund = async() => {
prøve {
sætIsLoading(sand);
sætFejl(nul);
const response = await fetch(«https://dog.ceo/api/breeds/image/random»);
hvis (!response.ok) {
throw new Error(«HTTP-fejl» + response.status);
}
const data = afvent svar.json();
sætImageUrl(data.besked);
} fange (fejl) {
sætFejl(fejl.besked);
} langt om længe {
sætIsLoading(false);
}
};
hentHund();
}, []);
if (indlæser) {
tilbagevenden Opladning… ;
}
if (fejl) {
tilbagevenden Fejl: {fejl} ;
}
Vend tilbage (
);
}
Bemærk at det er verificeret response.ok at skelne mellem korrekt svar (kode 200-299) og HTTP-fejlFetch afviser kun løfter på grund af netværksfejl, så hvis du ikke tjekker... ok Du tænker måske "alt er fint", selv med en 500.
Det samme mønster kan tilpasses til anmodninger, der afhænger af props. Hvis du f.eks. vil vise dataene for en bestemt bruger baseret på en userId der kommer ovenfra, ville du bruge den rekvisit som en afhængighed af effekten til opdater dataene hver gang de ændres.
Propelafhængige anmodninger og sikker aflysning under demontering
Når URL'en eller identiteten af de data, der skal indlæses, afhænger af en prop (f.eks. Genindlæs brugeren, hvis den ændrer sig props.userId), er vigtigt:
- Angiv egenskaben korrekt som en afhængighed af effekten.
- Undgå statusopdateringer, hvis komponenten fjernes før anmodningen slutter.
Et simpelt mønster for annullering er at bruge et internt flag:
import { useEffect, useState } from "react";
import axios from "axios";
funktion Bruger({ bruger-ID }) {
const [bruger, sætBruger] = brugStatus(null);
brugEffekt(() => {
lad erMounted = true;
const fetchData = async () => {
const resultat = afvent aksios(
`https://jsonplaceholder.typicode.com/users/${bruger-ID}`
);
hvis (erMounted) {
sætBruger(resultat.data);
}
};
hentData();
returner() => {
erMonteret = falsk;
};
}, [bruger-ID]);
hvis (!bruger) {
tilbagevenden Opladning… ;
}
Vend tilbage (
{brugernavn}
E-mailadresse: {bruger.email}
Telefon: {bruger.telefon}
);
}
Mens erModerne hav trueDet er tilladt at opdatere statusNår komponenten fjernes, sætter oprydningseffekten det flag. falseOg selvom løftet bliver opfyldt senere, setUser Den vil ikke længere blive udført. Der findes mere raffinerede alternativer (AbortController, databiblioteker osv.), men dette illustrative mønster gør ideen klar.
Genbrugslogik: flere effekter i den samme komponent
En af krogenes store styrker er, at Du er ikke begrænset til en enkelt brugseffekt pr. komponentFaktisk er det normalt at foretrække. opdeler logik i flere specialiserede effekter i stedet for at blande det hele sammen til én kæmpe.
Tænk på en komponent, der har begge dele en revisor, fjerndata og måske et abonnementI klassen ville du ende med livscyklusmetoder fulde af blandet kode: i componentDidMount Du lancerede underskriftsindsamlingen og abonnerede; i componentDidUpdate du tjekkede manuelt, hvad der havde ændret sig; componentWillUnmount Du har rengjort begge dele.
Med kroge kan du bruge én anvendelse/effekt for hver "idé" eller ansvar:
import { useState, useEffect } from "react";
const MinKomponent = () => {
const [count, setCount] = useState(0);
const [data, sætData] = useState(null);
brugEffekt(() => {
console.log(«Monteret komponent»);
}, []);
brugEffekt(() => {
console.log(`Antal er ændret til ${count}`);
}, [antal]);
brugEffekt(() => {
console.log("Dataene er ændret");
}, [data]);
Vend tilbage (
sætAntal((c) => c + 1)}>Klik her
);
};
således Hver effekt fokuserer kun på det, der interesserer den.Koden bliver mere læsbar, og det er nemmere at tilføje eller fjerne adfærd uden at ødelægge resten. React vil anvende alle komponenteffekter i den rækkefølge, du deklarerer dem.
Optimer ydeevne: afhængigheder og effekter, der ikke gentages for meget
Når en effekt udfører dyrt arbejde (API-forespørgsler, tunge beregninger, tredjepartsgengivelse...), Du ønsker ikke, at den kører ved hver komponentopdatering.I en klassekomponent blev dette ofte løst med manuelle sammenligninger. if (prevProps.x !== this.props.x) indenfor componentDidUpdate.
Med kroge, Optimering er indbygget i selve useEffect-signaturen. ved hjælp af afhængighedsarrayet. For eksempel, hvis du kun ønsker, at en effekt skal udløses, når den ændres countDu erklærer det som:
useEffect(() => {
console.log("Ha cambiado count");
}, [count]);
React vil sammenligne det gamle afhængighedsarray [valorAnteriorDeCount] med det nye [valorNuevoDeCount]. Hvis alle elementerne er ens, springes effekten over.Hvis der er nogen forskel, renses den forrige effekt (hvis der var en oprydning), og konfigurationen køres igen.
På samme måde, hvis du ønsker en effekt, der kun kører én gang for at initialisere noget (og eventuelt rydder det op ved adskillelse), Du bruger et tomt arrayMen vær forsigtig: De rekvisitter og tilstande, som du aflæser i effekten, vil bevare deres oprindelige værdier.Derfor bør dette mønster bruges, når du er sikker på, at din effekt ikke afhænger af, at noget ændrer sig.
For at undgå overseelser anbefales det kraftigt at aktivere reglen exhaustive-deps de eslint-plugin-react-hooksDenne regel analyserer effektens kode og advarer dig, hvis der mangler afhængigheder, og foreslår den passende løsning. Det er en meget effektiv måde at undgå subtile fejl, hvor forældede tilstandsværdier eller props bruges.
Typiske problemer med useEffect og hvordan man undgår dem
Det er simpelt at arbejde med effekter i teorien, men i praksis er der en række tilbagevendende fejl hvilket er noget man altid skal huske på for ikke at falde i dem igen og igen.
En af de mest omtalte er uendelig render-løkkeDet sker, når din effekt opdaterer en status, der er en del af dens afhængigheder på en måde, der ikke konvergerer. For eksempel, hvis du har:
useEffect(() => {
setCount(count + 1);
}, [count]);
I hver gengivelse, som f.eks. count Den ændrer sig, effekten kører igen, og du opdaterer tilstanden igen, og så videre i det uendelige. I disse tilfælde er du enten det forkerte sted (den opdatering bør ikke være i en effekt), eller også har du brug for andre typer logik, såsom et interval med oprydningeller brug den funktionelle form setCount(c => c + 1) uden at sætte count i lokalerne.
Et andet problem er følelsen af, at effekten "den kører to gange under montering"I strict mode udfører React under udvikling en slags "stresstest" ved at køre setup → cleanup → setup lige i starten. Dette sker ikke i produktion, men Det fremhæver dårligt designede effekter, der ikke renser ordentligt.Hvis din oprydningslogik er symmetrisk med din opsætningslogik, burde brugeren ikke bemærke noget usædvanligt.
Det er også almindeligt at se effekter, der gentages i hver gengivelse, selvom de har afhængigheder. Dette sker normalt fordi Arrayet indeholder objekter eller funktioner, der er oprettet i hver gengivelseDa referencer altid ændrer sig, antager React, at afhængigheden også har ændret sig. Løsningen: deklarer disse funktioner eller objekter inden for selve effekten eller huske dem med useCallback/useMemo hvis de virkelig er nødvendige udenfor.
useEffect, eksterne systemer og avanceret brug med effekthændelser
Årsagen til at være af useEffekt es Hold din komponent synkroniseret med eksterne systemer: sockets, kort, indlejrede videoafspillere, browser-API'er osv. Mønsteret er altid baseret på ideen om "når disse afhængigheder ændrer sig, genopret forbindelsen til de nye værdier og afbryd forbindelsen til de gamle."
For eksempel en komponent ChatRoom der forbinder sig til et bestemt rum afhængigt af serverUrl y roomId burde:
- Forbind under kørslen med den første kombination af
serverUrlyroomId. - Afbryd forbindelsen til det forrige rum og opret forbindelse til det nye, hvis brugeren skifter rum eller server.
- Frakobl helt, når komponenten afmonteres.
Med useEffect og en god oprydning kan du opnå det. hver gengivelse har sin sammenhængende forbindelse og at React kan gentage opsætnings-/oprydningssekvensen uden at noget hænger.
I mere avancerede scenarier kan du have brug for Læs den seneste tilstand og rekvisitter i en effekt uden at udløse genudførelserDet er her, at den slags mønstre kommer i spil. Effekthændelser (kroge som) useEffectEvent i de nye anbefalinger), som giver dig mulighed for at flytte bestemt ikke-reaktiv kode ud af afhængighedslisten. I mellemtiden er en praktisk tilgang brug brugerdefinerede referencer eller hooks for de stykker, hvor klassisk reaktiv adfærd ikke passer godt ind.
I forbindelse med server-side rendering (SSR) skal du huske på, at effekterne kører kun på klientenDen indledende HTML gengives på serveren, og når appen er hydreret i browseren, udløses useEffect-sætningerne. Hvis du kun har brug for at vise noget anderledes på klientsiden, kan du bruge en tilstand som didMount der går false a true i en effekt med [] og beting renderingen derfra.
At dominere useState y useEffekt I sidste ende handler det om at internalisere, at hver gengivelse er et snapshot af din komponent, at hooks er bundet til den rækkefølge, du deklarerer dem i, og at effekter beskriver, hvordan man synkroniserer dette snapshot med omverdenen, inklusive hvordan man fortryder det, der skal fortrydes. Med god afhængighedsstyring, oprydning og adskillelse af effekter efter ansvar, vil dine komponenter fungere meget mere forudsigeligt, forbruge færre ressourcer, og det vil være betydeligt lettere at finde ud af, hvad der kan være galt, når noget ikke opfører sig, som det skal.