Arvoihin viittaaminen Refillä
Kun haluat komponentin “muistavan” jotain tietoa, mutta et halua tiedon triggeröivän uudelleenrenderöintiä, voit käyttää refiä.
Tulet oppimaan
- Miten lisätä ref komponenttiisi
- Miten päivittää refin arvo
- Miten refit eroavat tilasta
- Miten käyttää refiä turvallisesti
Refin lisääminen komponenttiisi
Voit lisätä refin komponenttiisi importaamalla useRef
Hookin Reactista:
import { useRef } from 'react';
Komponenttisi sisällä kutsu useRef
hookkia ja välitä oletusarvo, jota haluat viitata ainoana argumenttina. Esimerkiksi, tässä on ref arvolla 0
:
const ref = useRef(0);
useRef
palauttaa seuraavanlaisen olion:
{
current: 0 // Arvo, jonka välitit useRef funktiolle
}
Illustrated by Rachel Lee Nabors
Pääset käsiksi nykyiseen refin arvoon ref.current
ominaisuuden kautta. Tämä arvo on tarkoituksella muokattavissa, eli voit sekä lukea että kirjoittaa siihen. Se on kuin salainen tasku komponentissasi, jota React ei seuraa. (Tämä on se mikä tekee refistä “pelastusluukun” Reactin yksisuuntaisesta datavirtauksesta—josta alla lisää!)
Täss, painike kasvattaa ref.current
arvoa joka kerta kun sitä painetaan:
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); }
Ref osoittaa numeroon, mutta, kuten tila, voit viitata mihin tahansa: merkkijonoon, olioon tai jopa funktioon. Tilaan verrattuna, ref on tavallinen JavaScript-olio, jolla on current
-ominaisuus, jota voit lukea ja muokata.
Huomaa, että komponentti ei renderöidy uudelleen joka kerta kun arvo kasvaa. Kuten tila, refit säilyvät Reactin uudelleenrenderöintien välillä. Kuitenkin, tilan asettaminen uudelleenrenderöi komponentin. Refin päivittäminen ei!
Esimerkki: sekuntikellon rakentaminen
Voit yhdistää refin ja tilan samaan komponenttiin. Esimerkiksi, tehdään sekuntikello, jonka käyttäjä voi käynnistää tai pysäyttää nappia painamalla. Jotta voidaan näyttää kuinka paljon aikaa on kulunut siitä kun käyttäjä on painanut “Start” nappia, sinun täytyy pitää kirjaa siitä milloin käyttäjä painoi “Start” nappia ja mitä nykyinen aika on. Tätä tietoa käytetään renderöinnissä, joten pidä se tilassa:
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
Kun käyttäjä painaa “Start”, käytät setInterval
funktiota päivittääksesi ajan joka 10 millisekuntin välien:
import { useState } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); function handleStart() { // Aloita laskeminen. setStartTime(Date.now()); setNow(Date.now()); setInterval(() => { // Päivitä tämänhetkinen aika joka 10ms välein. setNow(Date.now()); }, 10); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> </> ); }
Kun “Stop” nappia painetaan, sinun täytyy peruuttaa olemassa oleva ajastin, jotta se lopettaa now
tilamuuttujan päivittämisen. Voit tehdä tämän kutsumalla clearInterval
funktiota, mutta sinun täytyy antaa sille ajastimen ID, joka palautettiin aiemmin setInterval
kutsun kautta kun käyttäjä painoi “Start”. Sinun täytyy pitää ajastimen ID jossain. Koska ajastimen ID:tä ei käytetä renderöinnissä, voit pitää sen refissä:
import { useState, useRef } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> <button onClick={handleStop}> Stop </button> </> ); }
Kun tietoa käytetään renderöinnissä, pidä se tilassa. Kun tietoa tarvitaan vain tapahtumankäsittelijöissä ja sen muuttaminen ei vaadi uudelleenrenderöintiä, refin käyttäminen voi olla tehokkaampaa.
Refin ja tilan erot
Ehkäpä ajattelet, että refit vaikuttavat vähemmän “tiukilta” kuin tila—voit muokata niitä tilan asettamisfunktion käyttämisen sijaan. Mutta useimmissa tapauksissa haluat käyttää tilaa. Refit ovat “pelastusluukku”, jota et tarvitse usein. Tässä on miten tila ja refit vastaavat toisiaan:
ref | tila |
---|---|
useRef(initialValue) palauttaa { current: initialValue } | useState(initialValue) palauttaa tilamuuttujan nykyisen arvon ja tilan asetusfunktion ( [value, setValue] ) |
Ei triggeröi uudelleenrenderöintiä kun muutat sitä. | Triggeröi uudelleenrenderöinnin kun muutat sitä. |
Mutatoitavissa—voit muokata ja päivittää current :n arvoa renderöintiprosessin ulkopuolella. | Ei-mutatoitavissa—sinun täytyy käyttää tilan asetusfunktiota muokataksesi tilamuuttujaa jonottaaksesi uudelleenrenderöinti. |
Sinuun ei tulisi lukea (tai kirjoittaa) current arvoa kesken renderöinnin. | Voit lukea tilaa koska tahansa. Kuitenkin, jokaisella renderöinnillä on oma tilakuvansa tilasta, joka ei muutu. |
Tässä on laskuri-painike, joka on toteutettu tilalla:
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You clicked {count} times </button> ); }
Koska count
arvo näytetään, on järkevää käyttää tilaa arvon tallentamiseen. Kun laskurin arvo asetetaan setCount()
funktiolla, React renderöi komponentin uudelleen ja ruutu päivittyy vastaamaan uutta arvoa.
Jos yrität toteuttaa tämän refillä, React ei koskaan renderöi komponenttia uudelleen, joten et koskaan näe laskurin arvon muuttuvan! Katso miten tämän painikkeen klikkaaminen ei päivitä sen tekstiä:
import { useRef } from 'react'; export default function Counter() { let countRef = useRef(0); function handleClick() { // Tämä ei uudeleenrenderöi komponenttia! countRef.current = countRef.current + 1; } return ( <button onClick={handleClick}> You clicked {countRef.current} times </button> ); }
Tämä on syy miksi refin current
arvon lukeminen renderöinnin aikana johtaa epäluotettavaan koodiin. Jos tarvitset tätä, käytä tilaa sen sijaan.
Syväsukellus
Vaikka sekä useState
että useRef
on tarjottu Reactin puolesta, periaatteessa useRef
voitaisiin toteuttaa useState
:n päälle. Voit kuvitella, että Reactin sisällä useRef
on toteutettu näin:
// Reactin sisällä
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
Ensimmäisen renderöinnin aikana, useRef
palauttaa { current: initialValue }
. Tämä olio tallennetaan Reactin puolelle, joten seuraavalla renderöinnillä sama olio palautetaan. Huomaa, että tilan asetusfunktiota ei käytetä tässä esimerkissä. Se on tarpeeton, koska useRef
:n tarvitsee aina palauttaa sama olio!
React tarjoaa sisäänrakennetun version useRef
:sta koska se on tarpeeksi yleinen. Mutta voit ajatella sitä tavallisena tilamuuttujana ilman asetusfunktiota. Jos olet tutustunut olio-ohjelmointiin, refit voivat muistuttaa sinua instanssimuuttujista, mutta this.something
sijaan kirjoitat somethingRef.current
.
Milloin käyttää refiä
Tyypillisesti, käytät refiä kun komponenttisi täytyy “astua Reactin ulkopuolelle” ja kommunikoida ulkoisten rajapintojen kanssa—usein selaimen API:n, joka ei vaikuta komponentin ulkonäköön. Tässä on muutamia näitä harvinaisia tilanteita:
- Tallentaakseen ajastimen ID:t
- Tallentaakseen ja muokatakseen DOM elementtejä, joita käsittelemme seuraavalla sivulla
- Tallentaakseen muita oliota, jotka eivät ole tarpeellisia JSX:n laskemiseen.
Jos komponenttisi tarvitsee tallentaa arvoa, mutta se ei vaikuta renderöinnin logiikkaan, valitse ref.
Parhaat käytännöt refille
Näitä periaatteita noudattaen komponenteistasi tulee ennakoitavampia:
- Käsittele refejä kuten pelastusluukkua. Refit ovat hyödyllisiä kun työskentelet ulkoisten järjestelmien tai selaimen API:n kanssa. Jos suuri osa sovelluksesi logiikasta ja datavirtauksesta riippuu refeistä, saatat haluta miettiä lähestymistapaasi uudelleen.
- Älä lue tai kirjoita
ref.current
:iin kesken renderöinnin. Jos jotain tietoa tarvitaan kesken renderöinnin, käytä tilaa sen sijaan. Koska React ei tiedä milloinref.current
muuttuu, jopa sen lukeminen renderöinnin aikana tekee komponenttisi käyttäytymisestä vaikeasti ennakoitavaa. (Ainoa poikkeus tähän on koodi kutenif(!ref.current) ref.current = new Thing()
joka asettaa refin vain kerran ensimäisellä renderöinnillä.)
Reactin tilan rajoitukset eivät päde refiin. Esimerkiksi tila toimii tilannekuvana jokaiselle renderöinnille ja se ei päivity synkronisesti. Mutta kun muokkaat refin nykyistä arvoa, se muuttuu välittömästi:
ref.current = 5;
console.log(ref.current); // 5
Tämä johtuu siitä, että ref on tavallinen JavaScript olio, joten se käyttäytyy samoin.
Sinun ei myöskään tarvitse huolehtia mutaatioiden välttämistä, kun työskentelet refin kanssa. Jos olio, jota muutat ei ole käytössä renderöinnissä, React ei välitä mitä teet refin tai sen sisällön kanssa.
Ref ja DOM
Voit osoittaa refin mihin tahansa arvoon. Kuitenkin yleisin käyttökohde refille on DOM elementin käsittely. Esimerkiksi, tämä on kätevää jos haluat focusoida syöttölaatikon ohjelmakoodissa. Kun annat refin ref
-attribuuttiin JSX:ssä, kuten <div ref={myRef}>
, React asettaa vastaavan DOM elementin myRef.current
:iin. Voit lukea lisää tästä Manipulating the DOM with Refs.
Kertaus
- Refit ovat pelastusluukku arvojen pitämiseen, jotka eivät ole käytössä renderöinnissä. Et tarvitse niitä usein.
- Ref on perus JavaScript-olio, jolla on yksi ominaisuus nimeltään
current
, jonka voit lukea tai asettaa. - Voit pyytää Reactia antamaan sinulle refin kutsumalla
useRef
Hookia. - Kuten tila, refit antavat sinun säilyttää tietoa komponentin uudelleenrenderöinnin välillä.
- Toisin kuin tila, refin
current
-arvon asettaminen ei aiheuta uudelleenrenderöintiä. - Älä lue tai kirjota
ref.current
-arvoa renderöinnin aikana. Tämä tekee komponentistasi vaikeasti ennustettavan.
Haaste 1 / 4: Korjaa rikkinäinen chat-kenttä
Kirjoita viesti ja paina “Send” painiketta. Huomaat, että viesti ilmestyy kolmen sekunnin viiveellä. Tämän ajan aikana näet “Undo” painikkeen. Paina sitä. Tämä “Undo” painike on tarkoitettu pysäyttämään “Sent!” viesti näkyvistä. Se tekee tämän kutsumalla clearTimeout
funktiota timeout ID:llä, joka tallennettiin handleSend
funktion aikana. Kuitenkin, vaikka “Undo” painiketta painettaisiin, “Sent!” viesti ilmestyy silti. Etsi miksi se ei toimi, ja korjaa se.
import { useState } from 'react'; export default function Chat() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); let timeoutID = null; function handleSend() { setIsSending(true); timeoutID = setTimeout(() => { alert('Sent!'); setIsSending(false); }, 3000); } function handleUndo() { setIsSending(false); clearTimeout(timeoutID); } return ( <> <input disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <button disabled={isSending} onClick={handleSend}> {isSending ? 'Sending...' : 'Send'} </button> {isSending && <button onClick={handleUndo}> Undo </button> } </> ); }