- React-tilstanden må behandles som uforanderlig, med oppdateringer utført via settere i stedet for direkte mutasjon, spesielt for objekter og arrayer.
- Tilstandsoppdateringer er asynkrone og kan være batchbaserte, så bruk av funksjonelle oppdateringer unngår problemer med foreldede tilstander i tidtakere, nedleggelser og raske interaksjoner.
- Funksjonskomponenter med Hooks (useState, useRef og venner) er den moderne standarden, mens verktøy som React.memo og Immer hjelper med ytelse og nestede data.
- Tydelig separasjon av rekvisitter og tilstand, pluss en ovenfra-og-ned dataflytmodell, holder komponentoppførselen forutsigbar etter hvert som applikasjoner skaleres.

State er et av de React-konseptene som ser enkle ut på overflaten, men som raskt blir vanskelige etter hvert som appen din vokser. Du starter med en liten teller, og plutselig sjonglerer du flere skjemafelt, asynkrone oppdateringer, nestede objekter og ytelsesproblemer når alt gjengis på nytt samtidig. Det som skiller noen som «bruker React» fra noen som kan skalere og feilsøke React-applikasjoner i den virkelige verden, er å forstå tilstand i dybden.
I denne veiledningen skal vi gå gjennom den nåværende tilstanden i React (ordspill ment), fra klassekomponenter og livssyklusmetoder til moderne Hooks og uforanderlige oppdateringer. Vi skal også dykke ned i subtile, men kritiske emner som asynkrone oppdateringer, foreldede nedleggelser, når man skal bruke useRef i stedet for useState, og hvordan man holder brukergrensesnittet forutsigbart. Målet er å gi deg en klar mental modell slik at komponentene dine oppfører seg akkurat slik du forventer.
Fra rekvisitter til stat: hva hører egentlig hvor hjemme?
I kjernen av hver React-komponent er det to hoveddatakilder: props og state. rekvisitter sendes inn fra foreldrekomponenten og forblir faste i løpet av levetiden til den gjengivelsen, mens stat eies og kontrolleres av selve komponenten og er ment for data som endres over tid.
En god tommelfingerregel er: hvis data konfigureres utenfra og ikke endres i denne komponenten, er det en prop; hvis komponenten må spore og oppdatere den, er det state. Tenk deg en blinkende tekstkomponent: den faktiske teksten gis én gang (en prop), men om den vises for øyeblikket eller er skjult, veksler kontinuerlig (state). Denne forskjellen er det som lar React holde dataflyten forutsigbar og endirektiv.
React oppmuntrer til en ovenfra-og-ned (ensrettet) dataflyt der tilstanden befinner seg i den nærmeste felles forfaren som trenger å kontrollere den. En foreldrekomponent kan holde tilstand og sende verdier ned som rekvisitter til underkomponenter, som kan gjengi eller transformere dem, men trenger ikke å vite om disse verdiene opprinnelig kom fra tilstand, andre rekvisitter eller var hardkodet.
Det er derfor du ofte hører at tilstand er «lokal» eller «innkapslet». Bare komponenten som eier en del av tilstanden kan endre den, og ethvert brukergrensesnitt som er avledet fra den tilstanden flyter nedover gjennom props. Du kan fritt kombinere tilstandsfulle og tilstandsløse (rene) komponenter, og hvorvidt noe er tilstandsfullt regnes som en implementeringsdetalj som kan endre seg over tid.
Klassekomponenter: tilstand og livssyklus på den gamle måten
Før Hooks var den eneste måten å bruke tilstands- og livssyklusmetoder i React med ES6-klassekomponenter. Selv om de fleste moderne apper bruker funksjonskomponenter, vil du fortsatt se (og noen ganger vedlikeholde) klassekomponenter i mange kodebaser, så det er verdt å forstå hvordan de fungerer.
Å konvertere en funksjonskomponent som en enkel Clock inn i en klasse følger du noen mekaniske trinn. Du oppretter en klasse som utvider React.Component, Legg til en render() metode, flytt funksjonsdelen inn i render, erstatte props med this.props, og slett den opprinnelige funksjonen. Så lenge React fortsetter å gjengi <Clock /> inn i den samme DOM-noden, bruker den én enkelt forekomst av den klassen på nytt.
Å legge til en lokal tilstand i en klasse betyr å definere en konstruktør og tildele en initialverdi. this.state gjenstand. For eksempel kan du flytte en date verdi fra rekvisitter til tilstand ved å legge til en konstruktør som kaller super(props) og sett this.state = { date: new Date() }, og deretter erstatte enhver bruk av this.props.date in render() med this.state.dateHusk at i klassekomponenter skal du bare tildele direkte til this.state inne i konstruktøren.
Livssyklusmetoder er spesielle klassemetoder som React-kall på bestemte punkter i en komponents levetid. Når en komponent først settes inn i DOM-en (monteres), kaller React componentDidMount()Når den fjernes (avmonteres), kaller React componentWillUnmount()I det klassiske eksemplet med en tikkende klokke setter du opp en timer i componentDidMount og rydde det inn componentWillUnmount, lagrer timer-ID-en på this (for eksempel this.timerId), og ringer this.setState() hvert sekund for å oppdatere tiden.
Den typiske livssyklusen for en klokke ser slik ut: React kaller konstruktøren for å initialisere tilstanden, deretter render() å produsere DOM-en, deretter componentDidMount() hvor du starter timeren. Hver gang timeren går, kaller du setState(), som setter en oppdatering i kø og utløser render() med den nye tilstanden. Når komponenten er fjernet, componentWillUnmount() nullstiller timeren slik at du ikke lekker ressurser.
Å håndtere tilstand riktig i klasser betyr også å respektere tre viktige regler om setState. Du må ikke mutere this.state direkte, må du huske at oppdateringer kan være asynkrone og batchbaserte, og du bør forstå at oppdateringer slås sammen overfladisk (bare tilstandsnøkler på toppnivå slås sammen, ikke dypt nestede objekter).
Bruk av tilstand riktig: mutasjoner, asynkrone oppdateringer og dataflyt
En av de største kildene til forvirring for nybegynnere er at setState (og Hook-ekvivalenten) oppdaterer ikke tilstanden umiddelbart, og du bør aldri endre tilstandsobjekter på plass. React grupperer ofte flere oppdateringer sammen for ytelse, så begge deler this.state i klasser og tilstandsvariabler i Hooks gjenspeiler kanskje ikke den endelige tilstanden rett etter at du har planlagt en oppdatering.
Direkte muterende tilstand, som å gjøre this.state.count++ eller endring av egenskaper til et tilstandsobjekt, hopper over Reacts endringsdeteksjon og kan føre til at komponenter forblir låst på gamle verdier. React forventer at du behandler ethvert objekt i tilstanden som skrivebeskyttet. I stedet for å endre eksisterende objekter, oppretter du et nytt objekt eller en ny array med de ønskede endringene og sender det til tilstandsoppdateringen.
Fordi tilstandsoppdateringer kan være asynkrone, må du være forsiktig når du beregner neste tilstand fra den forrige. I klassene, noe sånt som this.setState({ count: this.state.count + 1 }) kan være feil hvis flere oppdateringer blir batchert. Løsningen er å bruke funksjonsformen: this.setState((prevState, props) => ({ count: prevState.count + 1 }))Dette garanterer at du jobber med det nyeste tilstandsbildet.
Det samme mønsteret eksisterer med Hooks: du kan kalle oppdateringsprogrammet med en funksjon i stedet for en verdi. For eksempel, setCount(prev => prev + 1) er den tryggere måten å øke en teller på hvis den nye verdien avhenger av den forrige, eller hvis oppdateringer kan skje i tidtakere eller hendelseshåndterere som kjører senere.
Selv om tilstanden er "lokal", beveger effekten av en tilstandsendring seg alltid nedover komponenttreet. En forelder-gjengivelse utløst av en tilstandsoppdatering vil også gjengi alle sine underordnede på nytt som standard. Denne ovenfra-og-ned-dataflyten er grunnleggende for Reacts mentale modell: én sannhetskilde øverst, brukergrensesnittet avledet fra den nedenfor.
Modern React: Hooks og funksjonskomponenter
Siden React 16.8 har Hooks blitt standardmåten å håndtere tilstand og bivirkninger i funksjonskomponenter. De lar deg bruke de samme mulighetene som klassekomponenter hadde (og flere) uten å skrive klasser eller håndtere this og livssyklusmetoder eksplisitt, apoyándose en el estado estable de JavaScript moderno.
Funksjonskomponenter er nå standardstilen i React-kodebaser. I stedet for å skrive class Example extends React.Component, definerer du en ren funksjon som function Example() { return <div />; }Når du trenger tilstand, bivirkninger eller referanser, «kobler du deg til» React via funksjoner som useState, useEffect og useRefHooker kan ikke brukes inne i klasser og må respektere reglene for hooker (kall dem alltid på toppnivået av komponenten din, aldri i løkker eller betingelser).
Ocuco useState Hook er den enkleste måten å legge til lokal tilstand til en funksjonskomponent. Den tar startverdien som et argument og returnerer et par: gjeldende tilstandsverdi og en setter. Takket være array-destrukturering skriver du vanligvis noe sånt som const = useState(0)React bevarer denne tilstanden mellom gjengivelser, noe som betyr at funksjonen kan kalles mange ganger, men tilstandsverdien huskes.
I motsetning til klassetilstand, verdien du beholder useState trenger ikke å være et objekt. Du kan lagre tall, strenger, boolske verdier, matriser eller objekter – hva enn som passer til dataene. Hvis du trenger flere uavhengige verdier, kan du kalle dem. useState flere ganger (for eksempel age, fruit, todosAlternativt kan du lagre et enkelt objekt og administrere flere egenskaper i det, men du må respektere regler for uforanderlighet når du oppdaterer.
Når du kaller setter-funksjonen som returneres av useState, du endrer ikke verdien synkront; du setter en oppdatering i kø akkurat som med setState i klasser. På neste rendering gir React komponenten din den nye tilstandsverdien. Derfor vil det å lese tilstanden umiddelbart etter å ha kalt setteren i den samme synkrone funksjonen fortsatt gi deg den gamle verdien.
Administrere objekter og nestede data i tilstand
React lar deg sette enhver JavaScript-verdi i tilstand, inkludert objekter og arrayer, men du må behandle dem som uforanderlige øyeblikksbilder. Primitive verdier som tall og strenger kan uansett ikke muteres, men objekter og arrayer kan teknisk sett – men å mutere dem bryter med Reacts antagelser og kan føre til subtile feil der komponenter ikke oppdateres.
Tenk deg et tilstandsobjekt som { x: 0, y: 0 } som representerer en pekerposisjon. Hvis du skriver position.x = event.clientX direkte, du har mutert det eksisterende objektet. React aner ikke at verdien endret seg fordi du aldri kalte setteren, så den vil ikke gjengis på nytt og brukergrensesnittet ditt sitter fast. Den riktige tilnærmingen er setPosition({ x: event.clientX, y: event.clientY }), som oppretter et helt nytt objekt og ber React om å gjengi med det.
Lokal mutasjon av nyopprettede objekter er helt greit. For eksempel kan du bygge opp et nytt objekt trinn for trinn: const next = { ...prev }; next.city = 'Paris'; så lenge som next var ikke allerede i tilstand. Mutasjon blir bare et problem når du endrer et objekt som allerede brukes i et tidligere tilstandsøyeblikksbilde, fordi andre deler av appen din fortsatt kan være avhengige av den gamle verdien.
For å bare oppdatere en del av et objekt mens du beholder resten, bruker du vanligvis objektspredningssyntaksen. For et formtilstandsobjekt som { firstName, lastName, email }, kan du håndtere endringer i inndata med noe sånt som setPerson({ ...person, : event.target.value })Dette kopierer de gamle egenskapene, og overskriver deretter bare den som endret seg. Oppslaget er grunt, så nestede objekter krever mer forsiktighet.
Dypt nestede objekter kan raskt føre til omfattende oppdateringskode, fordi du må opprette nye kopier langs hvert nivå av banen du endrer. Hvis for eksempel person.artwork.city endringer, ville du gjort setPerson({ ...person, artwork: { ...person.artwork, city: 'London' } })Under panseret finnes det ikke noe «nestet objekt»; det er separate objekter som peker mot hverandre, så hvis flere foreldreobjekter peker på det samme underobjektet og du muterer det, endrer du data på mer enn ett sted samtidig.
Hvis du stadig vekk skriver nestede oppslag, kan du vurdere å flate ut tilstandsformen din eller bruke et hjelpebibliotek som Immer. Immer lar deg skrive kode som ser mutativ ut (som draft.artwork.city = 'London') mens den produserer en ny, uforanderlig kopi for deg bak kulissene. I React kan du koble Immer med Hooks via useImmer fra use-immer pakke.
Tilstand i praksis: skjemaer, tidtakere og brukerinndata
I apper i den virkelige verden administrerer du sjelden tilstand bare for tellere; du administrerer brukerinput, API-svar og brukergrensesnitt-"moduser" som lasting, feil og suksess. Det viktigste tankesettet med React er at du ikke «manipulerer DOM-en» (for eksempel «deaktiver denne knappen»); i stedet beskriver du hvordan brukergrensesnittet skal se ut for hver tilstand og oppdaterer deretter tilstanden.
For eksempel kan en quiz- eller skjemakomponent spore en status tilstand som veksler mellom 'typing', 'submitting' og 'success'. JSX deaktiverer betinget send-knappen under innsending, og viser en suksessmelding når svaret er riktig. Du kaller aldri imperative DOM-metoder – React gjengir ganske enkelt på nytt med den nye tilstanden, og det visuelle resultatet endres.
Det er ved håndtering av skjemafelt at mange utviklere først møter forskjellen mellom sammenslåing av klassetilstander og useState adferd. I en klasse, setState slår sammen objektet du sender inn i det eksisterende tilstandsobjektet, slik at oppdatering av ett felt ikke fjerner de andre. Med useState, oppdateringer erstatter hele verdien: hvis tilstanden din er et objekt og du kaller setState({ email: '...' }), eventuelle andre egenskaper (som password) forsvinner med mindre du slår dem inn manuelt.
Denne forskjellen setter folk ut i fare når de refaktorerer fra flere primitive tilstandsvariabler til ett enkelt objekt. Hvis du endrer fra const og const til const og skriv deretter en generisk setForm({ : value }), vil du ende opp med et tilstandsobjekt som bare har ett felt. Løsningen er å spre det forrige objektet: setForm({ ...form, : value }).
I mer komplekse apper ringer du ofte ikke setState (eller setSomething) direkte fra overalt. Du kan sentralisere tilstanden ved hjelp av biblioteker som Redux eller MobX, eller bruke useReducer Hook for tilstandsmaskiner på komponentnivå. I disse oppsettene bruker du fortsatt de samme prinsippene for uforanderlighet; den eneste forskjellen er hvor og hvordan oppdateringer utføres.
Nye gjengivelser, ytelse og når useRef skal brukes
Hver tilstandsoppdatering i React utløser en ny gjengivelse av komponenten som eier tilstanden, og som standard alle dens underordnede. Dette er tilsiktet: gjengivelse på nytt er måten brukergrensesnittet ditt holder seg synkronisert med gjeldende data. Men det betyr også at tankeløs plassering av tilstander kan føre til unødvendig arbeid og trege brukergrensesnitt, spesielt når underordnede komponenter utfører dyre beregninger eller gjengir lange lister.
Tenk deg en app med et inndatafelt og en separat komponent som viser en lang liste med ferdigheter. Hvis den overordnede komponenten eier både teksten brukeren skriver og selve listen, vil hvert tastetrykk gjengi hele treet på nytt, inkludert ferdighetslisten, selv om listen ikke endret seg. Det er bortkastet innsats.
En enkel måte å optimalisere dette på er ved å pakke inn underkomponenter React.memo. React.memo er en komponent av høyere orden som husker resultatet av en funksjonskomponent: hvis propsene er de samme mellom gjengivelser, hopper React over å gjengi den på nytt. Så når ferdighetslistekomponenten din er pakket inn React.memo, vil ikke gjengis på nytt ved hvert tastetrykk – bare når skills rekvisitten faktisk endres (for eksempel når du legger til en ny ferdighet).
Ikke alle «tilstandslignende» data hører hjemme i useStatenoen ganger useRef er det bedre verktøyet. Ocuco useRef Hook gir deg et foranderlig objekt med en current egenskap som vedvarer i hele komponentens levetid, men oppdatering av den gjør det. ikke utløse en ny gjengivelse. Det gjør den perfekt for lagring av ting som timer-ID-er, DOM-elementreferanser eller tellere som du vil spore, men ikke trenger å vise i brukergrensesnittet.
Et enkelt eksempel er en teller implementert med useRef istedenfor useState. Hvis du lagrer tellingen i countRef.current og øker den i en hendelseshåndterer, endres den interne verdien, men den viste JSX-en oppdateres ikke fordi React ikke gjengis på nytt. Dette illustrerer den avgjørende forskjellen: useState er for verdier som styrer brukergrensesnittet; useRef er for verdier du vil beholde uten å påvirke gjengivelsen.
Uforanderlighet og hvorfor direkte mutasjon er en felle
Et grunnleggende prinsipp i React er at tilstandsoppdateringer må være uforanderlige. Det betyr ikke at du aldri kan endre noe; det betyr at i stedet for å endre eksisterende verdier (spesielt objekter og arrayer), oppretter du nye og lar de gamle stå som historiske øyeblikksbilder av brukergrensesnittet ditt.
Direkte muterende tilstand bryter forbindelsen mellom din mentale modell og det React gjør. Hvis du gjør noe sånt som state.count++ eller push direkte inn i en tilstandsarray, vil ikke React vite at noe har endret seg fordi du aldri kalte oppdatererfunksjonen. Det interne snapshotet React bruker for å bestemme når det skal gjengis på nytt forblir det samme, mens koden din tror at verdien har endret seg. Det er slik du får feil som «fikser seg selv» når du laster inn på nytt.
Du må også unngå å tilordne en tilstandsverdi til en annen variabel og deretter mutere den variabelen. For eksempel å gjøre const newCount = count; newCount++; muterer fortsatt den samme underliggende verdien for primitiver og for objekter, const copy = stateObj; oppretter ikke en kopi i det hele tatt – den oppretter bare en ny referanse til det samme objektet. Riktig kopiering krever mønstre som { ...stateObj } for gjenstander eller for arrayer.
Biblioteker som Redux, MobX (når de er konfigurert for uforanderlighet) eller Immer eksisterer delvis for å håndheve eller forenkle uforanderlige mønstre. Enten du bruker Reacts innebygde Hooks eller et tilstandsadministrasjonsbibliotek, gjelder den gylne regelen: aldri muter eksisterende tilstand hvis du forventer at React skal plukke opp endringen og gjengi den på nytt.
Asynkrone oppdateringer, batching og foreldet tilstand
En subtil, men viktig detalj om React-tilstand er at oppdateringer er asynkrone og planlagte, ikke implementeres umiddelbart. Når du ringer setState eller en kroksetter som setCount, React «setter» en ny rendering i kø for en stund i fremtiden. Den blokkerer ikke koden din der og da fra å oppdatere og gjengi på nytt umiddelbart, noe som lar React batch-oppdatere flere ganger og holde ytelsen jevn.
Denne planleggingsmodellen betyr at du ikke kan stole på lesestatus umiddelbart etter at du har kalt oppdateringsprogrammet i den samme synkrone blokken. Verdien du får vil vanligvis være det gamle øyeblikksbildet. I stedet bør du tenke på oppdateringsprogrammet som en forespørsel: «neste gang du gjengir, bruk denne verdien (eller denne transformasjonsfunksjonen)».
Dette er spesielt viktig når du oppdaterer tilstanden basert på gjeldende verdi innenfor nedleggelser som setTimeout eller tilbakeringinger av abonnementer. Disse tilbakekallingene fanger opp hva statusen var da de ble opprettet. Hvis du deretter gjør det setCount(count + 1) inne i en timeout, den count du refererer til kan være foreldet når tilbakeringingen faktisk kjører.
Dette fenomenet er kjent som «foreldet tilstand» eller «foreldede nedleggelser». Hvis du for eksempel har en knapp som, ved klikk, kaller en funksjon som setter en tidsavbrudd og deretter øker tilstanden etter ett sekund, kan det hende at flere raske klikk ikke øker tilstanden riktig. Hvert tilbakekall av tidsavbrudd bruker den gamle count den fanget opp når timeouten var planlagt.
Den robuste løsningen er å bruke det funksjonelle oppdateringsskjemaet til tilstandssetteren din. Istedenfor setCount(count + 1) innenfor tidsavbruddet skriver du setCount(prevCount => prevCount + 1)Nå mottar hver tilbakeringing den siste verdien da oppdateringen ble brukt, ikke den som tilfeldigvis var innenfor omfanget da tidsavbruddet ble opprettet. Dette eliminerer problemet med foreldet tilstand uten å endre oppførselen til lukkingene dine ellers.
Reacts dokumentasjon påpeker også en mindre kjent detalj: hvis den funksjonelle oppdateringen din ikke returnerer noe (undefined), vil React hoppe over ny rendering. Det betyr at oppdateringsfunksjonene dine alltid skal returnere neste tilstandsverdi (eller bruke den forrige på nytt) med mindre du eksplisitt vil forhindre en oppdatering – noe som sjelden er ønskelig med standard useState bruk.
Å forstå denne kombinasjonen av asynkron planlegging, batching og lukkingsatferd er avgjørende for å skrive pålitelig tilstandslogikk i apper som håndterer tidsavbrudd, intervaller, abonnementer eller raske brukerinteraksjoner. Når du har internalisert at tilstandssettere planlegger oppdateringer i stedet for å utføre dem umiddelbart, vil feil som pleide å føles tilfeldige begynne å gi mening.
Når du setter alle disse ideene sammen – rekvisitter vs. tilstand, klasselivssykluser vs. kroker, uforanderlighet, kontrollerte komponenter, useRef For ikke-visuelle verdier, memoisering, asynkrone oppdateringer og foreldede nedleggelser – ender du opp med en kraftig, forutsigbar modell for hvordan React UI-er utvikler seg over tid. I stedet for å tenke i form av viktige DOM-endringer, designer du klare tilstandsmodeller og lar React håndtere ny rendering, noe som gjør det enklere å resonnere rundt, teste og utvide komponentene dine etter hvert som applikasjonen din vokser.
