Menu

Hook useRef w React: refy do DOM i trwałe wartości

useRef daje komponentowi pudełko, { current }, które zachowuje zawartość między renderowaniami, a jego zmiana nie powoduje renderowania. Używaj go, żeby dostać się do węzła DOM (fokus, przewijanie, pomiar) albo przechować ID timera czy dowolną wartość, której nie pokazuje ekran.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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
  • initialValue jest używane tylko przy pierwszym renderowaniu. Dla refów do DOM jest to null.
  • React zwraca ten sam obiekt przy każdym renderowaniu. Zmienia się tylko current.
  • Odczyt i zapis current to 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

useStateuseRef
Zwraca[value, setValue]{ current: value }
ZmianaRenderuje komponent ponownieNie robi nic widocznego
Kiedy nowa wartość jest dostępnaW następnym renderowaniuOd razu
Zmieniasz przezSetterPrzypisanie, ref.current = x
Do czegoWszystkiego, co pokazuje ekranID 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ