- Reaktsiooni olekut tuleb käsitleda muutumatuna, kusjuures uuendused tuleb teha pigem setterite kui otsese mutatsiooni kaudu, eriti objektide ja massiivide puhul.
- Oleku värskendused on asünkroonsed ja neid saab partiidena esitada, seega funktsionaalsete värskenduste kasutamine väldib taimerites, sulgemistes ja kiiretes interaktsioonides esinevaid aegunud oleku probleeme.
- Konksudega funktsioonikomponendid (useState, useRef ja sõbrad) on tänapäevane standard, samas kui tööriistad nagu React.memo ja Immer aitavad jõudluse ja pesastatud andmetega.
- Rekvisiitide ja oleku selge eraldamine ning ülalt-alla suunatud andmevoo mudel hoiab komponentide käitumise rakenduste skaleerumisel prognoositavana.
Olek on üks neist Reacti kontseptsioonidest, mis pealtnäha lihtne tundub, kuid rakenduse kasvades muutub see kiiresti keeruliseks. Alustad väikese loenduriga, aga järsku pead žongleerima mitme vormivälja, asünkroonsete värskenduste, pesastatud objektide ja jõudlusprobleemidega, kui kõik korraga uuesti renderdatakse. Oleku sügav mõistmine eristab kedagi, kes „kasutab Reacti“, kellestki, kes suudab reaalseid Reacti rakendusi skaleerida ja siluda.
Selles juhendis vaatame Reacti praegust olekut (sõnamäng taotluslik), alates klassikomponentidest ja elutsüklimeetoditest kuni moodsate konksude ja muutumatute uuendusteni. Samuti süveneme peenematesse, kuid kriitilistesse teemadesse nagu asünkroonsed uuendused, aegunud sulgemised, millal kasutada useRefi useState'i asemel ja kuidas hoida oma kasutajaliides etteaimatavana. Eesmärk on anda teile selge mentaalne mudel, et teie komponendid käituksid täpselt nii, nagu te ootate.
Rekvisiitidest riigini: mis kuhu tegelikult kuulub?
Iga Reacti komponendi keskmes on kaks peamist andmeallikat: props ja state. rekvisiidid antakse üle vanemkomponendist ja jäävad selle renderduse eluea jooksul fikseerituks, samal ajal kui riik kuulub komponendile endale ja seda kontrollib see ning see on mõeldud aja jooksul muutuvate andmete jaoks.
Hea rusikareegel on: kui andmed on väljastpoolt konfigureeritud ja selles komponendis ei muutu, on see prop; kui komponent peab neid jälgima ja värskendama, on see olek. Kujutage ette vilkuvat tekstikomponenti: tegelik tekst kuvatakse üks kord (prop), kuid see, kas see on hetkel kuvatud või peidetud, muutub pidevalt (olek). See eristamine võimaldab Reactil hoida teie andmevoo etteaimatava ja ühesuunalisena.
React soodustab ülalt-alla (ühesuunalist) andmevoogu, kus olek asub lähimas ühises esivanemas, kes peab seda juhtima. Emakomponent saab hoida olekut ja edastada väärtusi propsidena lastele, kes võivad neid renderdada või teisendada, kuid ei pea teadma, kas need väärtused pärinesid algselt olekust, teistest propsidest või olid kõvakodeeritud.
Seepärast kuulebki sageli, et osariik on „kohalik” või „kapseldatud”. Ainult komponent, mis omab olekuosa, saab seda muuta ja iga sellest olekust tuletatud kasutajaliides liigub allapoole läbi propside. Olekupõhiseid ja olekuta (puhtaid) komponente saab vabalt kombineerida ning seda, kas miski on olekupõhine, peetakse rakenduse detailiks, mis võib aja jooksul muutuda.
Klassi komponendid: olek ja elutsükkel vanakooli viisil
Enne konksude kasutamist oli Reactis oleku- ja elutsüklimeetodite ainus viis kasutada ES6 klassi komponentidega. Kuigi enamik tänapäevaseid rakendusi tugineb funktsioonikomponentidele, näete (ja mõnikord haldate) klassikomponente siiski paljudes koodibaasides, seega tasub mõista, kuidas need töötavad.
Funktsioonikomponendi teisendamiseks lihtsaks Clock klassi sisenemiseks järgite mõnda mehaanilist sammu. Sa lood klassi, mis laieneb React.Component, Lisa render() meetod, liiguta funktsiooni keha sisse render, asendage props koos this.propsja kustuta algne funktsioon. Seni kuni React renderdab <Clock /> samasse DOM-sõlme, kasutab see selle klassi ühte eksemplari uuesti.
Klassile lokaalse oleku lisamine tähendab konstruktori defineerimist ja algse oleku määramist. this.state objekt Näiteks võite liigutada a date väärtus propsidest olekusse, lisades konstruktori, mis kutsub super(props) ja komplektid this.state = { date: new Date() }, asendades seejärel mis tahes kasutuse this.props.date in render() koos this.state.datePea meeles, et klassi komponentides peaksid sa määrama otse ainult this.state konstruktori sees.
Elutsükli meetodid on spetsiaalsed klassimeetodid, mida React kutsub komponendi eluea teatud punktides. Kui komponent esmakordselt DOM-i lisatakse (paigaldatakse), kutsub React välja componentDidMount()Kui see eemaldatakse (lahti ühendatakse), kutsub React välja componentWillUnmount()Klassikalise tiksuva kella näites seadistate taimeri componentDidMount ja puhasta see sisse componentWillUnmount, salvestades taimeri ID this (näiteks this.timerId) ja helistades this.setState() iga sekund aja värskendamiseks.
Selle kella tüüpiline elutsükkel näeb välja selline: React kutsub konstruktori oleku initsialiseerimiseks välja render() DOM-i loomiseks componentDidMount() kust taimeri käivitate. Iga kord, kui taimer käivitub, helistate setState(), mis paneb värskenduse järjekorda ja käivitab render() uue olekuga. Kui komponent on eemaldatud, componentWillUnmount() tühjendab taimeri, et ressursse ei lekiks.
Klassides olekute õige haldamine tähendab ka kolme olulise reegli järgimist setState. Sa ei tohi muteeruda this.state Otseselt öeldes peate meeles pidama, et värskendused võivad olla asünkroonsed ja partiidena edastatud ning peaksite mõistma, et värskendused ühendatakse pealiskaudselt (ühendatakse ainult tipptasemel olekuvõtmed, mitte sügavalt pesastatud objektid).
Oleku õige kasutamine: mutatsioonid, asünkroonsed värskendused ja andmevoog
Üks suurimaid segaduse allikaid algajatele on see, et setState (ja Hooki ekvivalent) ei uuenda olekut koheselt ning olekuobjekte ei tohiks kunagi kohapeal muuta. React pakkib jõudluse huvides sageli mitu värskendust kokku, seega mõlemad this.state Klassides ja olekumuutujates Hookides ei pruugi kohe pärast värskenduse ajastamist lõplikku olekut kajastada.
Otseselt muteeriv olek, näiteks tehes this.state.count++ Või olekuobjekti omaduste muutmine jätab vahele Reacti muudatuste tuvastamise ja võib põhjustada komponentide vanade väärtuste kinni jäämise. React eeldab, et käsitled kõiki olekus olevaid objekte kirjutuskaitstud objektidena. Olemasolevate objektide muutmise asemel lood uue objekti või massiivi soovitud muudatustega ja edastad selle oleku uuendajale.
Kuna olekute värskendused võivad olla asünkroonsed, peate eelmisest olekust järgmise arvutamisel olema ettevaatlik. Tunnis midagi sellist this.setState({ count: this.state.count + 1 }) võib olla vale, kui mitu värskendust on pakkitud. Lahendus on funktsionaalse vormi kasutamine: this.setState((prevState, props) => ({ count: prevState.count + 1 }))See tagab, et töötate uusima oleku hetktõmmisega.
Sama muster eksisteerib ka konksudega: uuendajat saab väärtuse asemel funktsiooniga kutsuda. Näiteks setCount(prev => prev + 1) on turvalisem viis loenduri suurendamiseks, kui uus väärtus sõltub eelmisest või kui uuendused võivad toimuda hiljem töötavate taimerite või sündmuste käitlejate sees.
Kuigi olek on „lokaalne“, levib oleku muutuse mõju alati komponentide puud mööda allapoole. Oleku värskenduse käivitatud vanema uuesti renderdamine renderdab vaikimisi uuesti ka kõik selle alamobjektid. See ülalt-alla suunatud andmevoog on Reacti mentaalse mudeli aluseks: üks tõeallikas ülaosas, sellest tuletatud kasutajaliides allpool.
Modern React: konksud ja funktsioonikomponendid
Alates React 16.8 versioonist on konksudest saanud funktsioonikomponentide oleku ja kõrvalmõjude haldamise standardne viis. Need võimaldavad teil kasutada samu võimalusi, mis olid klassikomponentidel (ja rohkemgi veel), ilma klasse kirjutamata või nendega tegelemata. this ja elutsükli meetodid selgesõnaliselt, apoyándose en el estado estable de JavaScript moderno.
Funktsioonikomponendid on nüüd Reacti koodibaasides vaikestiiliks. Kirjutamise asemel class Example extends React.Component, defineerid sa lihtsa funktsiooni, näiteks function Example() { return <div />; }Kui vajate olekut, kõrvalmõjusid või viiteid, siis „ühendute” Reactiga selliste funktsioonide kaudu nagu useState, useEffect ja useRefKonksusid ei saa klasside sees kasutada ja need peavad vastama konksude reegleid (kutsu neid alati välja oma komponendi kõrgeimal tasemel, mitte kunagi tsüklites ega tingimustes).
. useState Hook on lihtsaim viis funktsioonikomponendile kohaliku oleku lisamiseks. See võtab argumendina algväärtuse ja tagastab paari: praeguse oleku väärtuse ja setteri. Tänu massiivi struktureerimisele kirjutatakse tavaliselt midagi sellist const = useState(0)React säilitab selle oleku uuesti renderdamiste vahel, mis tähendab, et funktsiooni saab mitu korda kutsuda, kuid oleku väärtus jääb meelde.
Erinevalt klassiseisundist on väärtus, mida sa hoiad useState ei pea olema objekt. Saate salvestada numbreid, stringe, tõeväärtusi, massiive või objekte – mida iganes andmetele sobib. Kui vajate mitut sõltumatut väärtust, võite helistada funktsioonile useState mitu korda (näiteks age, fruit, todosTeise võimalusena saate salvestada ühe objekti ja hallata selle sees mitut omadust, kuid värskendamisel peate järgima muutmatuse reegleid.
Kui kutsute välja setter-funktsiooni, mille tagastab useState, sa ei muuda väärtust sünkroonselt; sa paned värskenduse järjekorda täpselt nagu puhul setState klassides. Järgmisel renderdamisel annab React teie komponendile uue oleku väärtuse. Seetõttu annab oleku lugemine kohe pärast setteri kutsumist samas sünkroonses funktsioonis ikkagi vana väärtuse.
Objektide ja pesastatud andmete haldamine olekus
React võimaldab sul lisada mis tahes JavaScripti väärtuse olekusse, sealhulgas objektid ja massiivid, kuid sa pead neid käsitlema muutumatute hetktõmmistena. Primitiivseid väärtusi, nagu numbreid ja stringe, ei saa niikuinii muteerida, kuid objekte ja massiive saab tehniliselt – nende muteerimine rikub aga Reacti eeldusi ja võib viia peente vigadeni, kus komponendid ei uuene.
Vaatleme olekuobjekti, näiteks { x: 0, y: 0 } mis tähistab kursori positsiooni. Kui sa kirjutad position.x = event.clientX otse, oled olemasolevat objekti muteerinud. Reactil pole aimugi, et väärtus muutus, kuna sa ei kutsunud kunagi setterit välja, seega see ei renderdu uuesti ja sinu kasutajaliides jääb kinni. Õige lähenemine on setPosition({ x: event.clientX, y: event.clientY }), mis loob täiesti uue objekti ja annab Reactile käsu sellega renderdada.
Värskelt loodud objektide lokaalne mutatsioon on täiesti okei. Näiteks saate uue objekti luua samm-sammult: const next = { ...prev }; next.city = 'Paris'; seni next ei olnud juba olekus. Mutatsioon muutub probleemiks ainult siis, kui muudate objekti, mida juba kasutatakse mõnes eelmises oleku hetktõmmises, kuna teie rakenduse teised osad võivad endiselt sellele vanale väärtusele tugineda.
Objekti ainult osa värskendamiseks, ülejäänud osa säilitades, kasutatakse tavaliselt objekti hajutamise süntaksit. Vormi oleku objekti jaoks, näiteks { firstName, lastName, email }, võiksite sisendmuudatusi hallata millegi sellisega nagu setPerson({ ...person, : event.target.value })See kopeerib vanad omadused ja kirjutab üle ainult selle, mis muudeti. Levitus on pealiskaudne, seega pesastatud objektid vajavad rohkem hoolt.
Sügavalt pesastatud objektid võivad kiiresti viia pika ja pika uuenduskoodini, kuna peate looma uued koopiad iga muudetava teekonna taseme ulatuses. Näiteks kui person.artwork.city muudatusi, mida sa teeksid setPerson({ ...person, artwork: { ...person.artwork, city: 'London' } })Sisuliselt puudub „pesastatud objekt”; on eraldi objektid, mis osutavad üksteisele, seega kui mitu vanemat osutavad samale alamobjektile ja te seda muudate, muudate andmeid korraga mitmes kohas.
Kui avastad, et kirjutad pidevalt pesastatud laotusi, võid kaaluda oleku kuju lamestamist või abiraamatukogu (nt Immer) kasutamist. Immer võimaldab teil kirjutada koodi, mis näeb välja mutatiivne (näiteks draft.artwork.city = 'London'), samal ajal kui see loob teile kulisside taga uue muutmatu koopia. Reactis saate Immeri siduda konksudega, kasutades useImmer alates use-immer pakendis.
Riik praktikas: vormid, taimerid ja kasutaja sisend
Reaalsetes rakendustes hallatakse harva ainult loendurite olekut; hallatakse kasutaja sisendit, API vastuseid ja kasutajaliidese „režiime”, nagu laadimine, viga ja edu. Reacti peamine mõtteviisi muutus seisneb selles, et te ei "manipuleeri DOM-iga" (näiteks "keela see nupp"); selle asemel kirjeldate, kuidas kasutajaliides peaks iga oleku jaoks välja nägema, ja seejärel värskendate olekut.
Näiteks võib viktoriin või vormikomponent jälgida status olek, mis lülitub vahel 'typing', 'submitting' ja 'success'. JSX keelab esitamise ajal tingimuslikult esitamise nupu ja kuvab eduteate, kui vastus on õige. Imperatiivseid DOM-meetodeid ei kutsuta kunagi välja – React lihtsalt renderdab uuesti uue olekuga ja visuaalne väljund muutub.
Vormiväljade käsitlemine on koht, kus paljud arendajad puutuvad esmakordselt kokku klassi olekute ühendamise ja useState käitumist. Ühes klassis setState liidab edastatud objekti olemasoleva olekuobjektiga, seega ühe välja värskendamine ei eemalda teisi. useState, asendavad värskendused kogu väärtuse: kui teie olek on objekt ja te kutsute setState({ email: '...' }), kõik muud omadused (nt password) kaovad, kui te neid käsitsi ei ühenda.
See erinevus ajab inimesed segadusse, kui nad mitmest primitiivsest olekumuutujast ühele objektile ümber faktoriseerivad. Kui te muudate const ja const et const ja seejärel kirjutage üldine setForm({ : value }), saate olekuobjekti, millel on alati ainult üks väli. Lahenduseks on eelmise objekti jaotamine: setForm({ ...form, : value }).
Keerukamates rakendustes te sageli ei helista setState (Või setSomething) otse igalt poolt. Võite oleku tsentraliseerida selliste teekide abil nagu Redux või MobX või kasutada useReducer Konks komponenditaseme olekumasinatele. Nendes seadistustes rakendatakse endiselt samu muutmatuse põhimõtteid; ainus erinevus seisneb selles, kus ja kuidas uuendusi tehakse.
Ümberrenderdamine, jõudlus ja millal kasutada funktsiooni useRef
Iga olekuvärskendus Reactis käivitab olekule kuuluva komponendi ja vaikimisi kõigi selle laste uuesti renderdamise. See on kavandatud: uuesti renderdamine on see, kuidas teie kasutajaliides püsib sünkroonis praeguste andmetega. Kuid see tähendab ka seda, et mõtlematu olekute paigutamine võib põhjustada tarbetut tööd ja aeglast kasutajaliidest, eriti kui alamkomponendid teevad kalleid arvutusi või renderdavad suuri loendeid.
Kujutage ette rakendust, millel on sisestusväli ja eraldi komponent, mis kuvab pikka oskuste loendit. Kui vanemkomponent omab nii kasutaja sisestatavat teksti kui ka loendit ennast, siis iga klahvivajutus renderdab kogu puu, sealhulgas oskuste loendi, uuesti, isegi kui see loend ei muutunud. See on raisatud vaev.
Üks lihtne viis selle optimeerimiseks on lapsekomponentide mähkimine React.memo. React.memo on kõrgema järgu komponent, mis salvestab funktsioonikomponendi tulemuse: kui selle rekvisiidid on renderdamiste vahel samad, siis React jätab selle uuesti renderdamise vahele. Seega teie oskuste loendi komponent, kui see on kord pakitud React.memo, ei renderdu iga klahvivajutuse korral uuesti – ainult siis, kui skills prop tegelikult muutub (näiteks uue oskuse lisamisel).
Mitte kõik „olekulaadsed” andmed ei kuulu sinna useState; mõnikord useRef on parem tööriist. . useRef Hook annab sulle muudetava objekti, millel on current omadus, mis püsib komponendi eluea jooksul, kuid selle uuendamine küll mitte käivitab uuesti renderdamise. See teeb selle ideaalseks selliste asjade salvestamiseks nagu taimerite ID-d, DOM-elementide viited või loendurid, mida soovite jälgida, kuid mida ei pea kasutajaliideses kuvama.
Lihtne näide on loendur, mis on rakendatud koos useRef asemel useState. Kui salvestate loenduse countRef.current ja suurendame seda sündmusekäitlejas, siis sisemine väärtus muutub, aga kuvatav JSX ei uuene, sest React ei renderdanud uuesti. See illustreerib olulist erinevust: useState on mõeldud väärtuste jaoks, mis juhivad kasutajaliidest; useRef on mõeldud väärtuste jaoks, mida soovite säilitada ilma renderdamist mõjutamata.
Muutumatus ja miks otsene mutatsioon on lõks
Reacti põhiprintsiip on, et oleku uuendused peavad olema muutmatud. See ei tähenda, et sa ei saa kunagi midagi muuta; see tähendab, et olemasolevate väärtuste (eriti objektide ja massiivide) muutmise asemel lood uusi ja lased vanadel jääda oma kasutajaliidese ajaloolisteks hetktõmmisteks.
Otseselt muteeriv olek katkestab seose teie mentaalse mudeli ja Reacti tegevuse vahel. Kui teed midagi sellist nagu state.count++ või otse olekumassiivi sisestades ei tea React, et midagi on muutunud, sest sa pole kunagi uuendajafunktsiooni kutsunud. Sisemine hetktõmmis, mida React kasutab uuesti renderdamise aja otsustamiseks, jääb samaks, samas kui sinu kood arvab, et väärtus on muutunud. Nii tekivadki vead, mis uuesti laadimisel „paranevad ise“.
Samuti peate vältima olekuväärtuse määramist teisele muutujale ja seejärel selle muutuja muteerimist. Näiteks tehes const newCount = count; newCount++; muteerib ikka sama alusväärtust nii primitiivide kui ka objektide puhul, const copy = stateObj; ei loo üldse koopiat – see loob lihtsalt sama objekti uue viite. Korrektne kopeerimine nõuab mustreid, näiteks { ...stateObj } esemete või massiivide jaoks.
Sellised teegid nagu Redux, MobX (kui need on konfigureeritud muutmatuks) või Immer eksisteerivad osaliselt muutmatute mustrite jõustamiseks või lihtsustamiseks. Olenemata sellest, kas kasutate Reacti sisseehitatud konkse või olekuhalduse teeki, kehtib kuldne reegel: ärge kunagi muteerige olemasolevat olekut kohapeal, kui eeldate, et React võtab muudatuse omaks ja renderdab uuesti.
Asünkroonsed värskendused, partiidena töötlus ja aegunud olek
Üks peen, aga oluline detail Reacti oleku kohta on see, et uuendused on asünkroonsed ja ajastatud, neid ei rakendata kohe. Kui helistate setState või konksukujuline setter nagu setCountReact „paneb järjekorda“ uuesti renderdamise mõneks ajaks tulevikus. See ei blokeeri teie koodi koheselt värskendamist ja uuesti renderdamist, mis võimaldab Reactil mitu värskendust partiidena genereerida ja jõudlust sujuvana hoida.
See ajastamismudel tähendab, et te ei saa loota lugemisolekule kohe pärast värskendaja kutsumist samas sünkroonses plokis. Saadud väärtus on tavaliselt vana hetktõmmis. Selle asemel peaksid uuendajat nägema kui päringut: „järgmisel renderdamisel kasuta seda väärtust (või seda teisendusfunktsiooni)“.
See on eriti oluline siis, kui uuendate olekut selle praeguse väärtuse põhjal sulgemiste seest, näiteks setTimeout või tellimuste tagasihelistamised. Need tagasihelistamised jäädvustavad oleku, mis oli nende loomise ajal. Kui te siis teete setCount(count + 1) ajalõpu ajal count , millele sa viitad, võib tagasihelistamise tegeliku käivitamise ajaks olla aegunud.
Seda nähtust tuntakse kui „seisvat olekut“ või „seisvaid sulgumisi“. Näiteks kui teil on nupp, mis klõpsamisel kutsub esile funktsiooni, mis määrab ajalõpu ja seejärel suurendab olekut ühe sekundi pärast, ei pruugi mitu kiiret klõpsu olekut õigesti suurendada. Iga ajalõpu tagasihelistamine kasutab vana count see jäädvustati ajalõpu ajastamise ajal.
Kindel lahendus on kasutada oma olekuseadistaja funktsionaalset uuendaja vormi. Asemel setCount(count + 1) ajalõpu sees kirjutad setCount(prevCount => prevCount + 1)Nüüd saab iga tagasihelistus värskenduse rakendamise ajal viimase eelmise väärtuse, mitte seda, mis juhtus ajalõpu loomise ajal ulatuses olema. See välistab aegunud oleku probleemi, muutmata teie sulgemiste käitumist muul viisil.
Reacti dokumentatsioon toob välja ka vähemtuntud detaili: kui teie funktsionaalne uuendaja ei tagasta midagi (undefined), React jätab uuesti renderdamise vahele. See tähendab, et teie värskendamisfunktsioonid peaksid alati tagastama järgmise oleku väärtuse (või taaskasutama eelmist), välja arvatud juhul, kui soovite värskendamist selgesõnaliselt vältida – see on standardsete funktsioonide puhul harva soovitav. useState kasutamine.
Selle asünkroonse ajastamise, partiidena töötlemise ja sulgemiskäitumise kombinatsiooni mõistmine on kriitilise tähtsusega usaldusväärse olekuloogika kirjutamiseks rakendustes, mis tegelevad ajalõpude, intervallide, tellimuste või kiirete kasutajainteraktsioonidega. Kui olete omaks võtnud, et olekute seadistajad ajastavad värskendusi, mitte ei teosta neid kohe, hakkavad varem juhuslikena tundunud vead loogiliseks muutuma.
Kui kõik need ideed kokku panna – rekvisiidid vs olek, klassi elutsüklid vs konksud, muutmatus, kontrollitud komponendid, useRef Mittevisuaalsete väärtuste, meeldejätmise, asünkroonsete värskenduste ja aegunud sulgemiste jaoks – saate võimsa ja ennustatava mudeli Reacti kasutajaliideste arengu kohta aja jooksul. Selle asemel, et mõelda pakiliste DOM-muudatuste peale, kujundate selged olekumudelid ja lasete Reactil uuesti renderdamisega hakkama saada, mis muudab teie komponentide arutlemise, testimise ja laiendamise rakenduse kasvades lihtsamaks.

