Referenziare valori con i ref
Quando vuoi che un componente “ricordi” un’informazione, ma non vuoi che quell’informazione avvii nuove renderizzazioni, puoi usare un ref.
Imparerai
- Come aggiungere un ref al tuo componente
- Come aggiornare il valore di un ref
- In cosa i ref differiscono dallo state
- Come usare i ref in sicurezza
Aggiungere un ref al tuo componente
Puoi aggiungere un ref al tuo componente importando l’Hook useRef da React:
import { useRef } from 'react';All’interno del tuo componente, chiama l’Hook useRef e passa il valore iniziale che vuoi referenziare come unico argomento. Ad esempio, ecco un ref al valore 0:
const ref = useRef(0);useRef restituisce un oggetto come questo:
{
current: 0 // Il valore che hai passato a useRef
}
Illustrato da Rachel Lee Nabors
Puoi accedere al valore corrente di quel ref tramite la proprietà ref.current. Questo valore è intenzionalmente mutabile, il che significa che puoi sia leggerlo che scriverci. È come una tasca segreta del tuo componente che React non traccia. (È questo che lo rende un “escape hatch” dal flusso di dati unidirezionale di React — ne parleremo più avanti!)
Qui, un bottone incrementerà ref.current a ogni click:
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> ); }
Il ref punta a un numero, ma, come lo state, potresti puntare a qualsiasi cosa: una stringa, un oggetto o persino una funzione. A differenza dello state, un ref è un normale oggetto JavaScript con la proprietà current che puoi leggere e modificare.
Nota che il componente non si ri-renderizza a ogni incremento. Come lo state, i ref vengono conservati da React tra le ri-renderizzazioni. Tuttavia, impostare lo state ri-renderizza un componente. Modificare un ref no!
Esempio: costruire un cronometro
Puoi combinare ref e state in un singolo componente. Ad esempio, creiamo un cronometro che l’utente può avviare o fermare premendo un bottone. Per mostrare quanto tempo è trascorso da quando l’utente ha premuto “Start”, dovrai tenere traccia di quando è stato premuto il bottone Start e di qual è l’ora corrente. Questa informazione viene usata per la renderizzazione, quindi la conserverai nello state:
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);Quando l’utente preme “Start”, userai setInterval per aggiornare l’ora ogni 10 millisecondi:
import { useState } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); function handleStart() { // Start counting. setStartTime(Date.now()); setNow(Date.now()); setInterval(() => { // Update the current time every 10ms. 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> </> ); }
Quando viene premuto il bottone “Stop”, devi annullare l’intervallo esistente in modo che smetta di aggiornare la variabile di state now. Puoi farlo chiamando clearInterval, ma devi passargli l’ID dell’intervallo restituito in precedenza dalla chiamata a setInterval quando l’utente ha premuto Start. Devi conservare l’ID dell’intervallo da qualche parte. Poiché l’ID dell’intervallo non viene usato per la renderizzazione, puoi conservarlo in un ref:
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> </> ); }
Quando un’informazione viene usata per la renderizzazione, conservala nello state. Quando un’informazione è necessaria solo ai gestori di eventi e modificarla non richiede una ri-renderizzazione, usare un ref può essere più efficiente.
Differenze tra ref e state
Forse stai pensando che i ref sembrino meno “rigidi” dello state — puoi mutarli invece di dover sempre usare una funzione d’impostazione dello state, per esempio. Ma nella maggior parte dei casi, vorrai usare lo state. I ref sono un “escape hatch” di cui non avrai spesso bisogno. Ecco come si confrontano state e ref:
| ref | state |
|---|---|
useRef(initialValue) restituisce { current: initialValue } | useState(initialValue) restituisce il valore corrente di una variabile di state e una funzione d’impostazione dello state ([value, setValue]) |
| Non avvia una ri-renderizzazione quando lo modifichi. | Avvia una ri-renderizzazione quando lo modifichi. |
Mutabile — puoi modificare e aggiornare il valore di current fuori dal processo di renderizzazione. | ”Immutabile” — devi usare la funzione d’impostazione dello state per modificare le variabili di state e mettere in coda una ri-renderizzazione. |
Non dovresti leggere (o scrivere) il valore di current durante la renderizzazione. | Puoi leggere lo state in qualsiasi momento. Tuttavia, ogni renderizzazione ha la propria istantanea dello state che non cambia. |
Ecco un bottone contatore implementato con lo state:
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> ); }
Poiché il valore di count viene visualizzato, ha senso usare una variabile di state per esso. Quando il valore del contatore viene impostato con setCount(), React ri-renderizza il componente e lo schermo si aggiorna per riflettere il nuovo conteggio.
Se provassi a implementarlo con un ref, React non ri-renderizzerebbe mai il componente, quindi non vedresti mai il conteggio cambiare! Guarda come cliccare questo bottone non aggiorna il suo testo:
import { useRef } from 'react'; export default function Counter() { let countRef = useRef(0); function handleClick() { // This doesn't re-render the component! countRef.current = countRef.current + 1; } return ( <button onClick={handleClick}> You clicked {countRef.current} times </button> ); }
Ecco perché leggere ref.current durante la renderizzazione porta a codice inaffidabile. Se ne hai bisogno, usa lo state.
Approfondimento
Sebbene sia useState che useRef siano forniti da React, in linea di principio useRef potrebbe essere implementato sopra useState. Puoi immaginare che all’interno di React, useRef sia implementato così:
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}Durante la prima renderizzazione, useRef restituisce { current: initialValue }. Questo oggetto viene conservato da React, quindi durante la renderizzazione successiva verrà restituito lo stesso oggetto. Nota come la funzione d’impostazione dello state non venga usata in questo esempio. È superflua perché useRef deve sempre restituire lo stesso oggetto!
React fornisce una versione integrata di useRef perché è abbastanza comune nella pratica. Ma puoi pensarlo come una normale variabile di state senza una funzione d’impostazione. Se hai familiarità con la programmazione orientata agli oggetti, i ref potrebbero ricordarti i campi di istanza — ma invece di this.something scrivi somethingRef.current.
Quando usare i ref
Tipicamente, userai un ref quando il tuo componente deve “uscire” da React e comunicare con API esterne — spesso un’API del browser che non influisce sull’aspetto del componente. Ecco alcune di queste situazioni rare:
- Conservare ID di timeout
- Conservare e manipolare elementi DOM, che trattiamo nella pagina successiva
- Conservare altri oggetti non necessari per calcolare il JSX.
Se il tuo componente deve conservare un valore, ma non influisce sulla logica di renderizzazione, scegli i ref.
Best practice per i ref
Seguire questi principi renderà i tuoi componenti più prevedibili:
- Tratta i ref come un escape hatch. I ref sono utili quando lavori con sistemi esterni o API del browser. Se gran parte della logica della tua applicazione e del flusso di dati si basa sui ref, potresti voler ripensare il tuo approccio.
- Non leggere o scrivere
ref.currentdurante la renderizzazione. Se un’informazione è necessaria durante la renderizzazione, usa lo state. Poiché React non sa quandoref.currentcambia, anche leggerlo durante la renderizzazione rende difficile prevedere il comportamento del componente. (L’unica eccezione a questo è codice comeif (!ref.current) ref.current = new Thing()che imposta il ref una sola volta durante la prima renderizzazione.)
Le limitazioni dello state di React non si applicano ai ref. Ad esempio, lo state si comporta come un’istantanea per ogni renderizzazione e non si aggiorna in modo sincrono. Ma quando muti il valore corrente di un ref, cambia immediatamente:
ref.current = 5;
console.log(ref.current); // 5Questo perché il ref stesso è un normale oggetto JavaScript, e quindi si comporta come tale.
Non devi neanche preoccuparti di evitare la mutazione quando lavori con un ref. Finché l’oggetto che stai mutando non viene usato per la renderizzazione, a React non importa cosa fai con il ref o il suo contenuto.
Ref e DOM
Puoi puntare un ref a qualsiasi valore. Tuttavia, il caso d’uso più comune per un ref è accedere a un elemento DOM. Ad esempio, è utile se vuoi mettere a fuoco un input programmaticamente. Quando passi un ref a un attributo ref in JSX, come <div ref={myRef}>, React inserirà l’elemento DOM corrispondente in myRef.current. Una volta che l’elemento viene rimosso dal DOM, React aggiornerà myRef.current impostandolo a null. Puoi leggere di più su questo in Manipolare il DOM con i ref.
Riepilogo
- I ref sono un escape hatch per conservare valori non usati per la renderizzazione. Non ne avrai spesso bisogno.
- Un ref è un normale oggetto JavaScript con una singola proprietà chiamata
current, che puoi leggere o impostare. - Puoi chiedere a React di darti un ref chiamando l’Hook
useRef. - Come lo state, i ref ti permettono di conservare informazioni tra le ri-renderizzazioni di un componente.
- A differenza dello state, impostare il valore
currentdi un ref non avvia una ri-renderizzazione. - Non leggere o scrivere
ref.currentdurante la renderizzazione. Questo rende il componente difficile da prevedere.
Sfida 1 di 4: Correggere un input chat rotto
Digita un messaggio e clicca “Send”. Noterai che c’è un ritardo di tre secondi prima di vedere l’alert “Sent!“. Durante questo ritardo, puoi vedere un bottone “Undo”. Cliccalo. Questo bottone “Undo” dovrebbe impedire la comparsa del messaggio “Sent!“. Lo fa chiamando clearTimeout per l’ID del timeout salvato durante handleSend. Tuttavia, anche dopo aver cliccato “Undo”, il messaggio “Sent!” compare comunque. Scopri perché non funziona e correggilo.
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> } </> ); }