useRef — это хук React, который создаёт мутируемый ref-объект с свойством .current, сохраняемый между рендерами компонента. В отличие от useState, изменение .current не вызывает ререндер, что делает useRef идеальным для хранения DOM-ссылок, таймеров и любых значений, которые должны переживать рендеры без перерисовки UI. По данным React Documentation (2025), ref-объект создаётся один раз за время жизни компонента и не меняется между рендерами, что гарантирует стабильность ссылки.
Главное
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.
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 initially
// countRef.current = 5 after mutation
// No re-render happens!
}
Самый распространённый сценарий использования useRef — получение прямого доступа к DOM-элементу. React присваивает elementRef.current ссылку на DOM-узел после монтирования компонента. Это необходимо для фокусировки input, измерения размеров элемента, интеграции с анимационными библиотеками и библиотеками, которые не используют React-рендеринг.
React автоматически управляет ref-ссылками на DOM: при монтировании присваивает actual DOM-узел, при размонтировании обнуляет (ставит null). Это гарантирует, что в .current всегда актуальное значение, и предотвращает утечки памяти через «висящие» ссылки на удалённые элементы. Никакой ручной очистки не требуется.
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 и без лишних перерисовок.
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 и useState — решается одним вопросом: «Нужен ли ререндер при изменении значения?». Если да — useState. Если нет — useRef. useState хранит состояние, влияющее на вывод компонента; useRef хранит данные, которые нужны для внутренней логики, но не влияют на UI.
На практике developers часто используют useRef для хранения callback-ов, чтобы избежать проблем с замыканиями. Например, если useEffect подписывается на событие, и внутри коллбэка нужен актуальный state — храни коллбэк в useRef. При каждом рендере обновляй ref.current новой функцией, и эффект будет вызывать всегда свежий коллбэк без переподписки.
| Критерий | useRef | useState |
|---|---|---|
| Ререндер | Не вызывает при изменении | Вызывает при каждом setState |
| Мутация | Прямая: ref.current = value | Через setter: setState(value) |
| Использование в JSX | Не используется (нет эффекта) | Используется в выводе компонента |
| Пример | Таймеры, DOM-ссылки, предыдущие значения | Данные формы, UI-состояние, флаги |
Существует anti-pattern: использовать useRef для хранения данных, которые нужны в JSX, но изменение которых не должно вызывать ререндер. Это приводит к рассинхронизации — UI показывает старые данные, хотя ref.current уже новый. Если значение отображается в UI — используй useState. Если только используется внутри логики — useRef.
Связка useRef + useEffect — стандартный паттерн для отслеживания предыдущих значений пропсов. Храни предыдущее значение в ref, сравнивай с текущим в useEffect и принимай решение на основе разницы. Это особенно полезно при анимациях, когда нужно знать, какое значение было до изменения.
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 до текущего значения.
Самая частая ошибка — чтение ref.current в рендер-фазе для вычисления JSX. Поскольку изменение ref.current не вызывает ререндер, компонент может использовать устаревшее значение. Если значение .current участвует в выводе UI — используй useState. Если нужно синхронизировать ref и state, используй useEffect для обновления state из ref.
// ❌ 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 только для вспомогательных значений, не участвующих в рендеринге.
Часто задаваемые вопросы
Да, это распространённый паттерн — previous state pattern. Создай ref и обновляй его в useEffect при каждом изменении отслеживаемого значения. В момент между рендером и эффектом ref.current содержит предыдущее значение, которое можно сравнить с текущим. Это не требует дополнительных ререндеров и работает с любыми типами данных.
React присваивает ref.current значение DOM-узла только после того, как элемент отрендерен и добавлен в реальный DOM. Во время первого рендера компонент ещё не смонтирован, поэтому ref.current равен начальному значению (null). Доступ к DOM через ref.current возможен только в useEffect или в обработчиках событий, которые вызываются после монтирования.
createRef создаёт новый ref-объект при каждом рендере — его нужно использовать только в классовых компонентах. useRef создаёт ref один раз и возвращает тот же объект при всех последующих рендерах. В функциональных компонентах используй исключительно useRef; createRef приведёт к потере значения при ререндере, так как объект будет пересоздан.
Да, ref-объект можно передавать как обычный проп: <Child inputRef={inputRef} />. Дочерний компонент использует его через inputRef.current. Однако для передачи ref напрямую в DOM-элемент дочернего компонента используй forwardRef — HOC, который позволяет пробросить ref через props. Без forwardRef ref не будет автоматически привязан к DOM-элементу внутри дочернего компонента.
Привяжи ref к элементу, затем в useEffect читай ref.current.getBoundingClientRect() или ref.current.offsetWidth / offsetHeight. Для реактивного отслеживания изменений размера используй ResizeObserver внутри useEffect: создавай observer, подписывайся на изменения и обновляй state с новыми размерами. Не забывай отключать observer в cleanup.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также