useRef zwraca obiekt, { current: initialValue }, który React przechowuje przez cały czas życia komponentu. Zmiana ref.current niczego nie renderuje ponownie. Dlatego refy nadają się do dwóch zadań: trzymania węzła DOM, żeby wywoływać na nim metody takie jak focus(), i zapamiętywania między renderowaniami wartości, której ekran nie musi pokazywać.
Przekazanie obiektu refa do ref={inputRef} mówi Reactowi, żeby umieścił węzeł DOM <input> w inputRef.current. Zmień focus() na select(), wpisz coś i kliknij ponownie: tekst zostanie zaznaczony.
Składnia
const myRef = useRef(initialValue);
myRef.current; // read
myRef.current = value; // write, no re-render
initialValuejest używane tylko przy pierwszym renderowaniu. Dla refów do DOM jest tonull.- React zwraca ten sam obiekt przy każdym renderowaniu. Zmienia się tylko
current. - Odczyt i zapis
currentto zwykły JavaScript. React tego nie śledzi.
Wartość, która nie powoduje renderowania
Stan służy do tego, co pokazuje ekran. Ref służy do tego, co komponent musi pamiętać, ale nie wyświetla. Ten blok trzyma jeden licznik w refie, a drugi w stanie, i wypisuje oba:
Kliknij kilka razy Ref + 1. Konsola pokazuje, że ref.current rośnie, ale strona wciąż pokazuje 0 i nie ma logu renderowania. Teraz kliknij State + 1: komponent się renderuje, a licznik refa na ekranie skacze do liczby, którą ref już miał. Ref cały czas trzymał swoją wartość; React po prostu nie miał powodu jej narysować.
Refy a stan
useState | useRef | |
|---|---|---|
| Zwraca | [value, setValue] | { current: value } |
| Zmiana | Renderuje komponent ponownie | Nie robi nic widocznego |
| Kiedy nowa wartość jest dostępna | W następnym renderowaniu | Od razu |
| Zmieniasz przez | Setter | Przypisanie, ref.current = x |
| Do czego | Wszystkiego, co pokazuje ekran | ID timerów, węzłów DOM, poprzednich wartości, flag |
Jeśli masz wątpliwości, sprawdź, czy JSX czyta tę wartość. Jeśli tak, należy do stanu.
Przechowywanie ID timera
setInterval zwraca ID, którego potrzebujesz później, żeby zatrzymać timer. Dobrze pasuje do refa: handler Stop go potrzebuje, ale ekran nigdy go nie pokazuje, a trzymanie go w zwykłej zmiennej zgubiłoby je przy następnym renderowaniu.
Zastąp useRef(null) przez let intervalRef = { current: null };, kliknij Start, odczekaj dwie sekundy, a potem Stop: timer działa dalej. Każde tyknięcie renderuje komponent, co tworzy świeży obiekt, więc Stop niczego nie czyści.
Zapamiętywanie poprzedniej wartości
Efekt uruchamia się po renderowaniu, więc może porównać nową wartość z tą zapisaną w refie, a potem zapisać nową na następny raz:
Kliknij green, potem blue: konsola wypisuje changed from red to green i changed from green to blue. Kliknięcie koloru, który już jest wybrany, niczego nie wypisuje, bo efekt uruchamia się tylko wtedy, gdy zmienia się color.
Praca z DOM
Refy pozwalają wywoływać API przeglądarki, dla których React nie ma propsa: focus(), select(), scrollIntoView(), play() na wideo albo odczyt rozmiaru przez getBoundingClientRect().
const lastItemRef = useRef(null);
function scrollToLast() {
lastItemRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
<li ref={lastItemRef}>Last item</li>
ref.current ma wartość null podczas pierwszego renderowania, bo React nie utworzył jeszcze węzła DOM, i znowu po usunięciu elementu. Odczytuj go w handlerach zdarzeń i efektach, gdzie węzeł istnieje. Żeby zmierzyć element i coś ustawić, zanim przeglądarka odmaluje ekran, odczytaj ref w useLayoutEffect.
Nie odczytuj ani nie zapisuj ref.current podczas renderowania
React oczekuje, że ciało komponentu to czyste obliczenie: te same propsy i stan, ten sam JSX. Ref zmienia się poza tym modelem, więc renderowanie, które go odczytuje albo zapisuje, może dać różne wyniki dla tych samych danych wejściowych, a React może wywoływać ciało częściej, niż się spodziewasz.
function Bad() {
const count = useRef(0);
count.current++; // writing during render
return <p>{count.current}</p>; // reading during render
}
Odczytuj i zapisuj refy w handlerach zdarzeń i efektach. Jedynym wyjątkiem jest leniwa inicjalizacja, gdy wypełniasz ref raz, przy pierwszym renderowaniu:
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer(); // runs once
}
(Demo z licznikiem w refie powyżej celowo odczytuje clicks.current w JSX, żeby pokazać, że ekran za nim nie podąża. Nie kopiuj tego do prawdziwego kodu.)
Ref callback
Zamiast obiektu refa możesz przekazać do ref funkcję. React wywołuje ją z węzłem DOM, gdy element zostaje dodany. W React 19 funkcja może zwrócić funkcję czyszczącą, którą React wywołuje przy usuwaniu elementu. Przydaje się to w liście, w której nie możesz wywołać useRef raz na element:
Przy ładowaniu konsola wypisuje jedną linię attached na każdy element. Kliknij Remove last, a pojawi się tylko detached Cheese. trackNode jest zdefiniowane poza komponentem, więc przy każdym renderowaniu to ta sama funkcja i React zostawia pozostałe elementy w spokoju. Napisz ją zamiast tego w linii jako ref={(node) => { ... }}, a każde kliknięcie odłączy i ponownie podłączy każdy element, bo każde renderowanie przekazuje nową funkcję.
Przekazywanie refa do własnego komponentu
W React 19 ref jest zwykłym propsem komponentów funkcyjnych. Rodzic tworzy ref, a dziecko umieszcza go na elemencie DOM, który chce udostępnić:
Przed React 19 wymagało to forwardRef, które nadal działa. Strona o forwardRef pokazuje obie wersje i to, jak udostępnić tylko kilka metod przez useImperativeHandle.
Częste błędy
Pokazywanie wartości refa w JSX. Ekran za nią nie podąża. Jeśli użytkownik ma widzieć wartość, użyj stanu.
Używanie ref.current jako zależności efektu. [ref.current] odczytuje wartość podczas renderowania, a React nie wie, kiedy się ona zmienia, więc efekt nie uruchamia się ponownie, gdy to nastąpi. Żeby zareagować na podłączenie węzła, użyj ref callback.
Odczyt refa do DOM podczas pierwszego renderowania. Jest null, dopóki React nie utworzy węzła. Odczytuj go w efekcie albo handlerze.
Tworzenie nowego obiektu zamiast używania hooka. const ref = { current: null } w ciele komponentu to świeży obiekt przy każdym renderowaniu. Tylko useRef za każdym razem daje ten sam.
Najczęściej zadawane pytania
Co zwraca useRef?
Zwykły obiekt z jedną właściwością, current, ustawioną na przekazaną wartość początkową. React zwraca ten sam obiekt przy każdym renderowaniu, więc cokolwiek umieścisz w current, następnym razem nadal tam będzie.
Czym różni się useRef od useState?
Zmiana stanu renderuje komponent ponownie; zmiana ref.current nie. Używaj stanu dla wartości, które pokazuje ekran, a refa dla wartości potrzebnych tylko twoim handlerom zdarzeń i efektom, takich jak ID timera albo węzeł DOM.
Dlaczego ref.current ma wartość null?
React ustawia ref.current na węzeł DOM po utworzeniu węzła, w fazie zatwierdzania (commit). Podczas pierwszego renderowania jest jeszcze null i znowu jest null po usunięciu elementu. Odczytuj go w handlerze zdarzenia albo efekcie.
Czy zmiana ref.current powoduje ponowne renderowanie?
Nie. React nie wie, że go zmieniłeś. Nowa wartość pojawia się na ekranie tylko wtedy, gdy coś innego, na przykład aktualizacja stanu, ponownie wyrenderuje komponent.
Jak przekazać ref do komponentu dziecka?
W React 19 komponent funkcyjny dostaje ref jako zwykły prop, więc przekazujesz <MyInput ref={inputRef} />, a dziecko umieszcza go na swoim <input>. Starsze wersje wymagały forwardRef.