useRef — что это, хук ссылки и работа с DOM в React

Автор: IT Sectr Опубликовано: 2026-07-04 Время чтения: 9 мин

useRef — это хук React, который создаёт мутируемый ref-объект с свойством .current, сохраняемый между рендерами компонента. В отличие от useState, изменение .current не вызывает ререндер, что делает useRef идеальным для хранения DOM-ссылок, таймеров и любых значений, которые должны переживать рендеры без перерисовки UI. По данным React Documentation (2025), ref-объект создаётся один раз за время жизни компонента и не меняется между рендерами, что гарантирует стабильность ссылки.

Главное

  • useRef — хук для создания мутируемого ref-объекта с полем .current.
  • Нет ререндера — изменение .current не вызывает перерисовку компонента.
  • DOM-ссылки — передаётся в ref-проп элемента для прямого доступа к DOM-узлу.
  • Хранение значений — подходит для таймеров, предыдущих значений и любых данных между рендерами.
  • Стабильная ссылка — ref-объект не меняется между рендерами, в отличие от замыканий.

Что такое useRef в React

useRef — это хук, добавленный в React 16.8, который возвращает мутируемый ref-объект с единственным свойством .current. Начальное значение присваивается .current при монтировании компонента. Главное отличие от обычной переменной — ref-объект переживает ререндеры: при повторном рендере React не пересоздаёт ref, а возвращает тот же самый объект.

Внутренняя реализация useRef использует тот же механизм, что и useState и useEffect — связанный список хуков на fiber-узле. Однако в отличие от useState, React не отслеживает изменения ref-объекта, не ставит обновления в очередь и не планирует ререндер. Это делает useRef чрезвычайно лёгким хуком, не влияющим на производительность даже при частых изменениях.

По данным React Team — Hooks FAQ (2025), useRef — это по сути «коробка», которая хранит значение. Синтаксический сахар: const ref = useRef(initialValue) эквивалентен const ref = { current: initialValue } с тем отличием, что React гарантирует, что ref-объект будет тем же самым при каждом рендере. Никакой магии — просто стабильная ссылка, управляемая React.

jsx
import { useRef } from 'react';

function Component() {
    const countRef = useRef(0);
    // countRef.current = 0 initially
    // countRef.current = 5 after mutation
    // No re-render happens!
}

Доступ к DOM-элементам через useRef

Самый распространённый сценарий использования useRef — получение прямого доступа к DOM-элементу. React присваивает elementRef.current ссылку на DOM-узел после монтирования компонента. Это необходимо для фокусировки input, измерения размеров элемента, интеграции с анимационными библиотеками и библиотеками, которые не используют React-рендеринг.

React автоматически управляет ref-ссылками на DOM: при монтировании присваивает actual DOM-узел, при размонтировании обнуляет (ставит null). Это гарантирует, что в .current всегда актуальное значение, и предотвращает утечки памяти через «висящие» ссылки на удалённые элементы. Никакой ручной очистки не требуется.

jsx
function AutoFocusInput() {
    const inputRef = useRef(null);

    useEffect(() => {
        // Focus input after component mounts
        inputRef.current?.focus();
    }, []);

    return <input ref={inputRef} type="text" />;
}

По данным React — Refs and DOM documentation (2025), не используй ref для декларативных операций — открытия/закрытия модалок, управления видимостью. Для этих целей существуют state и пропсы. Refs предназначены для императивных операций, которые нельзя выразить декларативно: фокус, выделение текста, интеграция со сторонними библиотеками.

Хранение мутируемых значений без ререндеров

Второй по важности сценарий — хранение произвольных значений, которые должны сохраняться между рендерами, но изменение которых не должно вызывать ререндер. Типичные примеры: идентификаторы таймеров (setInterval/setTimeout), флаги для отмены запросов, предыдущие значения пропсов для сравнения и любые данные, не влияющие на вывод UI.

Особенно полезно это в useEffect: useRef хранит идентификатор таймера, а cleanup его очищает. Если хранить timerId в useState, каждый вызов setTimerId вызывал бы лишний ререндер, который не нужен ни для логики, ни для UI. useRef решает эту проблему без overhead и без лишних перерисовок.

jsx
function Timer() {
    const intervalRef = useRef(null);

    const start = () => {
        intervalRef.current = setInterval(() => {
            console.log('tick');
        }, 1000);
    };

    const stop = () => {
        clearInterval(intervalRef.current);
    };

    useEffect(() => stop, []);

    return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}

useRef vs useState: когда что использовать

Главная дилемма — выбор между useRef и useState — решается одним вопросом: «Нужен ли ререндер при изменении значения?». Если да — useState. Если нет — useRef. useState хранит состояние, влияющее на вывод компонента; useRef хранит данные, которые нужны для внутренней логики, но не влияют на UI.

На практике developers часто используют useRef для хранения callback-ов, чтобы избежать проблем с замыканиями. Например, если useEffect подписывается на событие, и внутри коллбэка нужен актуальный state — храни коллбэк в useRef. При каждом рендере обновляй ref.current новой функцией, и эффект будет вызывать всегда свежий коллбэк без переподписки.

КритерийuseRefuseState
РерендерНе вызывает при измененииВызывает при каждом setState
МутацияПрямая: ref.current = valueЧерез setter: setState(value)
Использование в JSXНе используется (нет эффекта)Используется в выводе компонента
ПримерТаймеры, DOM-ссылки, предыдущие значенияДанные формы, UI-состояние, флаги

Существует anti-pattern: использовать useRef для хранения данных, которые нужны в JSX, но изменение которых не должно вызывать ререндер. Это приводит к рассинхронизации — UI показывает старые данные, хотя ref.current уже новый. Если значение отображается в UI — используй useState. Если только используется внутри логики — useRef.

useRef в комбинации с useEffect

Связка useRef + useEffect — стандартный паттерн для отслеживания предыдущих значений пропсов. Храни предыдущее значение в ref, сравнивай с текущим в useEffect и принимай решение на основе разницы. Это особенно полезно при анимациях, когда нужно знать, какое значение было до изменения.

jsx
function PriceDisplay({ price }) {
    const prevPriceRef = useRef(price);

    useEffect(() => {
        const prevPrice = prevPriceRef.current;
        if (price > prevPrice) {
            animateUp();
        } else if (price < prevPrice) {
            animateDown();
        }
        prevPriceRef.current = price;
    }, [price]);

    return <span>${price}</span>;
}

По данным React Documentation — Hooks FAQ (2025), этот паттерн называют «previous value pattern». Он работает, потому что useRef сохраняет значение между рендерами, а useEffect выполняется после фиксации изменений. Сначала обновляется DOM с новым price, затем useEffect сравнивает с предыдущим (которое ещё хранится в ref) и обновляет ref.current до текущего значения.

Типичные ошибки с useRef

Самая частая ошибка — чтение ref.current в рендер-фазе для вычисления JSX. Поскольку изменение ref.current не вызывает ререндер, компонент может использовать устаревшее значение. Если значение .current участвует в выводе UI — используй useState. Если нужно синхронизировать ref и state, используй useEffect для обновления state из ref.

  • Ref в JSX — чтение ref.current внутри рендер-функции приводит к рассинхронизации UI и данных.
  • Ref как зависимость useEffect — ref.current не должен быть в массиве зависимостей; React не отслеживает его изменения.
  • Забытый null check — при размонтировании ref.current становится null; проверяй перед доступом.
  • Хранение функций в ref — обновляй ref.current при каждом рендере, если используешь паттерн со свежим коллбэком.
jsx
// ❌ Don't read ref.current during render for display
function BadComponent() {
    const valRef = useRef(0);
    return <p>{valRef.current}</p>; // will NOT update on mutation
}

// ✅ Use state for display, ref for logic
function GoodComponent() {
    const [val, setVal] = useState(0);
    const valRef = useRef(0);
    return <p>{val}</p>;
}

Ещё одна распространённая ошибка — использование useRef в качестве единственного способа хранения состояния в компоненте, когда данные всё-таки влияют на UI. Разработчики выбирают useRef, чтобы избежать «лишних» ререндеров, но в итоге получают UI, который не обновляется. Правильный подход: используй useState для UI-данных и useRef только для вспомогательных значений, не участвующих в рендеринге.

Часто задаваемые вопросы

Можно ли использовать useRef для хранения предыдущего состояния?

Да, это распространённый паттерн — previous state pattern. Создай ref и обновляй его в useEffect при каждом изменении отслеживаемого значения. В момент между рендером и эффектом ref.current содержит предыдущее значение, которое можно сравнить с текущим. Это не требует дополнительных ререндеров и работает с любыми типами данных.

Почему ref.current — null при первом рендере?

React присваивает ref.current значение DOM-узла только после того, как элемент отрендерен и добавлен в реальный DOM. Во время первого рендера компонент ещё не смонтирован, поэтому ref.current равен начальному значению (null). Доступ к DOM через ref.current возможен только в useEffect или в обработчиках событий, которые вызываются после монтирования.

В чём разница между useRef и createRef?

createRef создаёт новый ref-объект при каждом рендере — его нужно использовать только в классовых компонентах. useRef создаёт ref один раз и возвращает тот же объект при всех последующих рендерах. В функциональных компонентах используй исключительно useRef; createRef приведёт к потере значения при ререндере, так как объект будет пересоздан.

Можно ли передавать useRef через пропсы?

Да, ref-объект можно передавать как обычный проп: <Child inputRef={inputRef} />. Дочерний компонент использует его через inputRef.current. Однако для передачи ref напрямую в DOM-элемент дочернего компонента используй forwardRef — HOC, который позволяет пробросить ref через props. Без forwardRef ref не будет автоматически привязан к DOM-элементу внутри дочернего компонента.

Как измерить размер элемента с помощью useRef?

Привяжи ref к элементу, затем в useEffect читай ref.current.getBoundingClientRect() или ref.current.offsetWidth / offsetHeight. Для реактивного отслеживания изменений размера используй ResizeObserver внутри useEffect: создавай observer, подписывайся на изменения и обновляй state с новыми размерами. Не забывай отключать observer в cleanup.

Итоги

  • useRef — хук для создания мутируемого ref-объекта, сохраняемого между рендерами без вызова ререндера.
  • DOM-доступ — ref-проп передаётся элементу для прямого доступа к DOM-узлу; доступен после монтирования.
  • Хранение данных — таймеры, callback-и, флаги и любые значения, не влияющие на UI, храни в ref.
  • useRef vs useState — выбирай useState, если изменение должно перерисовать UI; useRef — если нет.
  • Previous value pattern — храни предыдущее значение в ref, обновляй в useEffect для сравнения.
  • Ref в JSX — не читай ref.current в рендер-фазе; это приводит к рассинхронизации UI и данных.
  • forwardRef — используй для передачи ref через пропсы в дочерний компонент.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также