- L'estat de reacció s'ha de tractar com a immutable, amb actualitzacions realitzades mitjançant setters en lloc de mutacions directes, especialment per a objectes i matrius.
- Les actualitzacions d'estat són asíncrones i es poden processar per lots, de manera que l'ús d'actualitzadors funcionals evita problemes d'estat obsolet en temporitzadors, tancaments i interaccions ràpides.
- Els components de funció amb Hooks (useState, useRef i altres) són l'estàndard modern, mentre que eines com React.memo i Immer ajuden amb el rendiment i les dades imbricades.
- Una separació clara dels accessoris i l'estat, a més d'un model de flux de dades de dalt a baix, manté el comportament dels components predictible a mesura que les aplicacions escalen.
L'estat és un d'aquells conceptes de React que sembla simple a primera vista, però es complica ràpidament a mesura que l'aplicació creix. Comences amb un petit comptador i, de sobte, estàs fent malabarismes amb diversos camps de formulari, actualitzacions asíncrones, objectes imbricats i problemes de rendiment quan tot es torna a renderitzar alhora. Comprendre l'estat en profunditat és el que diferencia algú que "utilitza React" d'algú que pot escalar i depurar aplicacions React del món real.
En aquesta guia, repassarem l'estat actual de React (joc de paraules intencionat), des dels components de classe i els mètodes del cicle de vida fins als Hooks moderns i les actualitzacions immutables. També aprofundirem en temes subtils però crítics com les actualitzacions asíncrones, els tancaments obsolets, quan utilitzar useRef en lloc de useState i com mantenir la interfície d'usuari predictible. L'objectiu és donar-vos un model mental clar perquè els vostres components es comportin exactament com espereu.
Dels arguments a l'estat: què pertany realment a on?
Al nucli de cada component de React hi ha dues fonts de dades principals: els props i l'estat. accessoris es passen des del component principal i romanen fixos durant la vida útil d'aquest renderitzat, mentre que van ser és propietat del propi component i està controlat per aquest i està pensat per a dades que canvien amb el temps.
Una bona regla general és: si les dades es configuren des de fora i no canvien en aquest component, és un accessori; si el component les ha de rastrejar i actualitzar, és l'estat. Imagineu un component de text intermitent: el text real es proporciona una vegada (un accessori), però si està actualment mostrat o ocult, canvia contínuament (estat). Aquesta distinció és el que permet a React mantenir el flux de dades predictible i unidireccional.
React fomenta un flux de dades de dalt a baix (unidireccional) on l'estat resideix en l'avantpassat comú més proper que l'ha de controlar. Un component pare pot contenir l'estat i passar valors com a accessoris als fills, que poden renderitzar-los o transformar-los però no necessiten saber si aquests valors provenen originalment de l'estat, d'altres accessoris o si estaven codificats de manera fixa.
Per això sovint sentireu que l'estat és "local" o "encapsulat". Només el component que posseeix una part de l'estat pot canviar-lo, i qualsevol interfície d'usuari derivada d'aquest estat flueix cap avall a través dels accessoris. Podeu combinar lliurement components amb estat i sense estat (purs), i si alguna cosa té estat es considera un detall d'implementació que pot canviar amb el temps.
Components de la classe: estat i cicle de vida a la manera tradicional
Abans de Hooks, l'única manera d'utilitzar mètodes d'estat i de cicle de vida a React era amb components de classe ES6. Tot i que la majoria d'aplicacions modernes es basen en components de funció, encara veureu (i de vegades mantindreu) components de classe en moltes bases de codi, per la qual cosa val la pena entendre com funcionen.
Per convertir un component de funció com un simple Clock a una classe, segueixes uns quants passos mecànics. Crees una classe que estén React.Component, afegiu un render() mètode, mou el cos de la funció a render, substituir props amb this.props, i suprimeix la funció original. Sempre que React continuï renderitzant <Clock /> al mateix node DOM, reutilitza una única instància d'aquesta classe.
Afegir un estat local a una classe significa definir un constructor i assignar-li un estat inicial. this.state objecte. Per exemple, podeu moure un date valor dels accessoris a l'estat afegint un constructor que crida super(props) i conjunts this.state = { date: new Date() }, substituint aleshores qualsevol ús de this.props.date in render() amb this.state.dateRecordeu que a la classe els components només els heu d'assignar directament a this.state dins del constructor.
Els mètodes del cicle de vida són mètodes de classe especial que fan crides a React en punts específics de la vida d'un component. Quan un component s'insereix per primera vegada al DOM (s'ha muntat), React fa crides componentDidMount()Quan s'elimina (desmunta), React fa una crida. componentWillUnmount()En l'exemple clàssic del rellotge que fa tic-tac, configureu un temporitzador a componentDidMount i esborra-ho componentWillUnmount, emmagatzemant l'ID del temporitzador a this (per exemple this.timerId), i trucant this.setState() cada segon per actualitzar l'hora.
El cicle de vida típic d'aquest rellotge és el següent: React crida el constructor per inicialitzar l'estat i, a continuació, render() per produir el DOM, aleshores componentDidMount() on inicieu el temporitzador. Cada vegada que s'activa el temporitzador, truqueu setState(), que posa en cua una actualització i la desencadena render() amb el nou estat. Un cop s'ha eliminat el component, componentWillUnmount() esborra el temporitzador per evitar pèrdues de recursos.
Gestionar correctament l'estat a les classes també significa respectar tres regles importants sobre setState. No has de mutar this.state directament, heu de recordar que les actualitzacions poden ser asíncrones i per lots, i heu d'entendre que les actualitzacions es fusionen superficialment (només es fusionen les claus d'estat de nivell superior, no els objectes imbricats profundament).
Ús correcte de l'estat: mutacions, actualitzacions asíncrones i flux de dades
Una de les majors fonts de confusió per als principiants és que setState (i l'equivalent a Hook) no actualitza l'estat immediatament i mai no hauríeu de canviar els objectes d'estat al seu lloc. React sovint agrupa diverses actualitzacions juntes per millorar el rendiment, de manera que ambdues this.state a les classes i les variables d'estat dels Hooks poden no reflectir l'estat final just després de programar una actualització.
Estat de mutació directa, com fer this.state.count++ o modificar les propietats d'un objecte d'estat, omet la detecció de canvis de React i pot fer que els components es quedin bloquejats en valors antics. React espera que tracteu qualsevol objecte de l'estat com a només lectura. En lloc de canviar els objectes existents, creeu un nou objecte o matriu amb els canvis desitjats i els passeu a l'actualitzador d'estat.
Com que les actualitzacions d'estat poden ser asíncrones, cal anar amb compte a l'hora de calcular el següent estat a partir de l'anterior. A les classes, alguna cosa així com this.setState({ count: this.state.count + 1 }) pot ser incorrecte si es fan múltiples actualitzacions per lots. La solució és utilitzar la forma funcional: this.setState((prevState, props) => ({ count: prevState.count + 1 }))Això garanteix que esteu treballant amb la darrera instantània d'estat.
El mateix patró existeix amb els Hooks: podeu cridar l'actualitzador amb una funció en lloc d'un valor. Per exemple, setCount(prev => prev + 1) és la manera més segura d'incrementar un comptador si el nou valor depèn de l'anterior o si les actualitzacions poden produir-se dins de temporitzadors o controladors d'esdeveniments que s'executen més tard.
Tot i que l'estat és "local", l'efecte d'un canvi d'estat sempre viatja cap avall per l'arbre de components. Un re-renderització d'un element principal activat per una actualització d'estat també re-renderitzarà tots els seus fills per defecte. Aquest flux de dades de dalt a baix és fonamental per al model mental de React: una font de veritat a la part superior, i la interfície d'usuari derivada d'aquesta a la part inferior.
React modern: ganxos i components de funció
Des de React 16.8, els Hooks s'han convertit en la manera estàndard de gestionar l'estat i els efectes secundaris en els components de funció. Et permeten utilitzar les mateixes capacitats que tenien els components de la classe (i més) sense escriure classes ni tractar amb this i mètodes de cicle de vida explícitament, apoyándose en el estat estable de JavaScript modern.
Els components de funció ara són l'estil per defecte a les bases de codi React. En lloc d'escriure class Example extends React.Component, defineixes una funció simple com function Example() { return <div />; }Quan necessiteu estat, efectes secundaris o referències, us "enganxeu" a React mitjançant funcions com ara useState, useEffect i useRefEls hooks no es poden utilitzar dins de les classes i han de respectar les Regles dels Hooks (invoca'ls sempre al nivell superior del component, mai en bucles o condicions).
L' useState El hook és la manera més senzilla d'afegir estat local a un component de funció. Pren el valor inicial com a argument i retorna un parell: el valor de l'estat actual i un setter. Gràcies a la desestructuració de matrius, normalment s'escriu alguna cosa com const = useState(0)React conserva aquest estat entre els renders, cosa que significa que la funció es pot cridar moltes vegades però el valor de l'estat es recorda.
A diferència de l'estat de classe, el valor que mantens en useState no ha de ser un objecte. Podeu emmagatzemar nombres, cadenes, valors booleans, matrius o objectes, el que s'adapti a les dades. Si necessiteu diversos valors independents, podeu cridar useState diverses vegades (per exemple, age, fruit, todos). Alternativament, podeu emmagatzemar un sol objecte i gestionar-hi diverses propietats, però heu de respectar les regles d'immutabilitat en actualitzar-lo.
Quan crideu la funció setter que retorna useState, no esteu canviant el valor de manera síncrona; esteu posant en cua una actualització igual que amb setState a les classes. En el següent renderitzat, React dóna al teu component el nou valor d'estat. És per això que llegir l'estat immediatament després de cridar el setter dins de la mateixa funció síncrona encara et donarà el valor antic.
Gestió d'objectes i dades imbricades en estat
React et permet posar qualsevol valor de JavaScript en estat, inclosos objectes i matrius, però els has de tractar com a instantànies immutables. Els valors primitius com els nombres i les cadenes no es poden mutar de totes maneres, però els objectes i les matrius tècnicament sí que poden; tanmateix, mutar-los incompleix les suposicions de React i pot provocar errors subtils on els components no s'actualitzen.
Considereu un objecte d'estat com { x: 0, y: 0 } representant la posició d'un punter. Si escrius position.x = event.clientX directament, has mutat l'objecte existent. React no té ni idea que el valor ha canviat perquè mai has cridat el setter, de manera que no es tornarà a renderitzar i la interfície d'usuari es queda bloquejada. L'enfocament correcte és setPosition({ x: event.clientX, y: event.clientY }), que crea un objecte nou i indica a React que el renderitzi amb això.
La mutació local d'objectes recentment creats està perfectament bé. Per exemple, podeu construir un objecte nou pas a pas: const next = { ...prev }; next.city = 'Paris'; sempre que next no estava ja en aquest estat. La mutació esdevé un problema només quan es canvia un objecte que ja s'està utilitzant en alguna instantània d'estat anterior, ja que altres parts de l'aplicació encara poden dependre d'aquest valor antic.
Per actualitzar només una part d'un objecte i conservar la resta, normalment s'utilitza la sintaxi de dispersió d'objectes. Per a un objecte d'estat de formulari com ara { firstName, lastName, email }, podríeu gestionar els canvis d'entrada amb alguna cosa com ara setPerson({ ...person, : event.target.value })Això copia les propietats antigues i després només sobreescriu la que ha canviat. La dispersió és superficial, de manera que els objectes imbricats requereixen més cura.
Els objectes imbricats profundament poden conduir ràpidament a un codi d'actualització prolix, perquè cal crear noves còpies al llarg de cada nivell de la ruta que esteu canviant. Per exemple, si person.artwork.city canvis, tu faries setPerson({ ...person, artwork: { ...person.artwork, city: 'London' } })Sota el capó, no hi ha cap "objecte imbricat"; hi ha objectes separats que apunten entre si, de manera que si diversos pares apunten al mateix objecte fill i el mutes, estàs canviant dades en més d'un lloc alhora.
Si us trobeu escrivint constantment fulls de càlcul imbricats, podeu considerar aplanar la forma de l'estat o utilitzar una biblioteca auxiliar com Immer. Immer et permet escriure codi que sembla mutatiu (com draft.artwork.city = 'London') mentre produeix una nova còpia immutable entre bastidors. A React, podeu emparellar Immer amb Hooks mitjançant useImmer des use-immer paquet.
Estat a la pràctica: formularis, temporitzadors i entrada de l'usuari
En aplicacions del món real, rarament es gestiona l'estat només per als comptadors; es gestiona l'entrada de l'usuari, les respostes de l'API i els "modes" de la interfície d'usuari com ara la càrrega, l'error i l'èxit. El canvi de mentalitat clau amb React és que no "manipuleu el DOM" (per exemple, "desactiveu aquest botó"); en comptes d'això, descriviu com hauria de ser la interfície d'usuari per a cada estat i després actualitzeu l'estat.
Per exemple, un component de qüestionari o formulari pot fer un seguiment d'un status estat que alterna entre 'typing', 'submitting' i 'success'. El JSX desactiva condicionalment el botó d'enviament durant l'enviament i mostra un missatge d'èxit un cop la resposta és correcta. Mai no es criden mètodes DOM imperatius: React simplement es torna a renderitzar amb el nou estat i la sortida visual canvia.
La gestió dels camps del formulari és on molts desenvolupadors troben per primera vegada la diferència entre la fusió d'estats de classes i useState comportament En una classe, setState fusiona l'objecte que passeu amb l'objecte d'estat existent, de manera que l'actualització d'un camp no elimina els altres. Amb useState, les actualitzacions substitueixen tot el valor: si el vostre estat és un objecte i truqueu setState({ email: '...' }), qualsevol altra propietat (com ara password) desapareixeran tret que els fusioneu manualment.
Aquesta diferència fa ensopegar les persones quan refactoritzen des de múltiples variables d'estat primitives a un sol objecte. Si canvies de const i const a const i després escriu un genèric setForm({ : value }), acabaràs amb un objecte d'estat que només té un camp. La solució és estendre l'objecte anterior: setForm({ ...form, : value }).
En aplicacions més complexes, sovint no trucareu setState (o setSomething) directament des de tot arreu. Podeu centralitzar l'estat utilitzant biblioteques com Redux o MobX, o utilitzar el useReducer Hook per a màquines d'estat a nivell de component. En aquestes configuracions, encara apliqueu els mateixos principis d'immutabilitat; l'única diferència és on i com es realitzen les actualitzacions.
Rerenders, rendiment i quan utilitzar useRef
Cada actualització d'estat a React desencadena un re-renderitzat del component que posseeix l'estat i, per defecte, de tots els seus fills. Això és per disseny: el re-renderitzat és la manera com la interfície d'usuari es manté sincronitzada amb les dades actuals. Però també significa que la col·locació d'estats imprudent pot causar treball innecessari i interfícies d'usuari lentes, especialment quan els components fills fan càlculs costosos o renderitzen llistes grans.
Imagineu una aplicació amb un camp d'entrada i un component separat que mostra una llarga llista d'habilitats. Si el component principal és el propietari tant del text que l'usuari està escrivint com de la llista en si, cada pulsació de tecla tornarà a renderitzar tot l'arbre, inclosa la llista d'habilitats, tot i que aquesta llista no hagi canviat. Això és un esforç inútil.
Una manera senzilla d'optimitzar això és embolicant els components fills en React.memo. React.memo és un component d'ordre superior que memoritza el resultat d'un component de funció: si els seus accessoris són els mateixos entre renders, React omet el seu renderitzat. Així doncs, el vostre component de llista d'habilitats, un cop embolicat en React.memo, no es tornarà a renderitzar en cada pulsació de tecla, només quan el skills la propietat realment canvia (per exemple, quan afegeixes una nova habilitat).
No totes les dades "semblants a un estat" pertanyen a useState; de vegades useRef és la millor eina. L' useRef Hook et dóna un objecte mutable amb un current propietat que persisteix durant tota la vida útil del component, però actualitzar-la sí que ho fa no activar un re-renderitzat. Això el fa perfecte per emmagatzemar coses com ara ID de temporitzador, referències d'elements DOM o comptadors que voleu rastrejar però que no cal que es mostrin a la interfície d'usuari.
Un exemple senzill és un comptador implementat amb useRef en lloc de useState. Si guardeu el recompte a countRef.current i l'incrementeu en un controlador d'esdeveniments, el valor intern canvia, però el JSX que es mostra no s'actualitzarà perquè React no s'ha tornat a renderitzar. Això il·lustra la diferència crucial: useState és per a valors que impulsen la interfície d'usuari; useRef és per a valors que voleu mantenir sense afectar el renderitzat.
Immutabilitat i per què la mutació directa és una trampa
Un principi fonamental de React és que les actualitzacions d'estat han de ser immutables. Això no vol dir que no puguis canviar mai res; vol dir que, en comptes de modificar els valors existents (especialment els objectes i les matrius), en crees de nous i deixes que els antics es mantinguin com a instantànies històriques de la teva interfície d'usuari.
La mutació directa de l'estat trenca la connexió entre el teu model mental i el que fa React. Si fas alguna cosa com state.count++ o si ho envies directament a una matriu d'estats, React no sabrà que res ha canviat perquè mai has cridat la funció d'actualització. La instantània interna que React utilitza per decidir quan tornar a renderitzar es manté igual, mentre que el teu codi creu que el valor ha canviat. Així és com s'obtenen errors que "es corregeixen sols" quan tornes a carregar.
També cal evitar assignar un valor d'estat a una altra variable i després mutar aquesta variable. Per exemple, fent const newCount = count; newCount++; encara muta el mateix valor subjacent per a les primitives i per als objectes, const copy = stateObj; no crea cap còpia, sinó que crea una altra referència al mateix objecte. Una còpia correcta requereix patrons com ara { ...stateObj } per a objectes o per a matrius.
Biblioteques com Redux, MobX (quan es configuren per a la immutabilitat) o Immer existeixen en part per fer complir o simplificar patrons immutables. Tant si feu servir els Hooks integrats de React com una biblioteca de gestió d'estats, la regla d'or és la següent: no muteu mai l'estat existent si espereu que React capti el canvi i el torni a renderitzar.
Actualitzacions asíncrones, processament per lots i estat obsolet
Un detall subtil però crucial sobre l'estat de React és que les actualitzacions són asíncrones i programades, no s'apliquen immediatament. Quan truques setState o un col·locador de ganxos com setCountReact posa a la cua un renderitzat durant un temps futur. No bloqueja el codi allà mateix per actualitzar-se i tornar a renderitzar-se immediatament, cosa que permet a React processar per lots múltiples actualitzacions i mantenir un rendiment fluid.
Aquest model de planificació significa que no podeu confiar en l'estat de lectura immediatament després de cridar l'actualitzador dins del mateix bloc síncron. El valor que obtindreu normalment serà la instantània antiga. En comptes d'això, hauríeu de pensar en l'actualitzador com una sol·licitud: "la propera vegada que renderitzeu, utilitzeu aquest valor (o aquesta funció de transformació)".
Això és especialment important quan actualitzeu l'estat en funció del seu valor actual des de dins de tancaments com ara setTimeout o devolucions de trucada de subscripció. Aquestes retrollamades capturen qualsevol estat que tingués en el moment en què es van crear. Si després ho feu setCount(count + 1) dins d'un temps mort, el count a què et refereixes podria estar obsolet en el moment en què la funció de retorn s'executa realment.
Aquest fenomen es coneix com a "estat obsolet" o "tancaments obsolets". Per exemple, si teniu un botó que, en fer clic, crida una funció que estableix un temps d'espera i després incrementa l'estat després d'un segon, és possible que diversos clics ràpids no incrementin l'estat correctament. Cada crida de retorn de temps d'espera utilitza l'antic count es va capturar quan es va programar el temps d'espera.
La solució robusta és utilitzar el formulari d'actualització funcional del vostre configurador d'estat. En lloc d' setCount(count + 1) dins del temps límit, escrius setCount(prevCount => prevCount + 1)Ara cada callback rep el valor anterior més recent en el moment en què s'aplica l'actualització, no el que estava dins de l'abast quan es va crear el temps d'espera. Això elimina el problema de l'estat obsolet sense canviar el comportament dels tancaments.
La documentació de React també assenyala un detall menys conegut: si l'actualitzador funcional no retorna res (undefined), React ometrà el re-renderitzat. Això vol dir que les funcions d'actualització sempre haurien de retornar el següent valor d'estat (o reutilitzar l'anterior) tret que vulgueu explícitament evitar una actualització, cosa que rarament és desitjable amb l'estàndard. useState ús.
Comprendre aquesta combinació de programació asíncrona, processament per lots i comportament de tancament és fonamental per escriure una lògica d'estat fiable en aplicacions que gestionen temps d'espera, intervals, subscripcions o interaccions ràpides dels usuaris. Un cop internalitzeu que els configuradors d'estat programen actualitzacions en lloc de realitzar-les immediatament, els errors que abans semblaven aleatoris començaran a tenir sentit.
Quan poses totes aquestes idees juntes: accessoris vs estat, cicles de vida de classe vs hooks, immutabilitat, components controlats, useRef per a valors no visuals, memorització, actualitzacions asíncrones i tancaments obsolets, s'obté un model potent i predictible de com evolucionen les interfícies d'usuari de React al llarg del temps. En lloc de pensar en termes de canvis imperatius al DOM, dissenyeu models d'estat clars i deixeu que React gestioni el re-renderitzat, cosa que facilita el raonament, la prova i l'ampliació dels components a mesura que l'aplicació creix.

