Олег Марков
Что такое useRef и как его применять в React
Введение
Современный React предлагает мощные инструменты управления состоянием и жизненным циклом компонентов, и одним из таких инструментов является хук useRef. Возможно, вы уже использовали useState или useEffect, чтобы работать с состояниями и побочными эффектами. Но зачем нужен еще один хук? useRef позволяет сохранить ссылку на значение между рендерами компонента и при этом не инициирует повторный рендер, когда это значение изменяется. Кроме того, useRef часто применяется для доступа к DOM-элементам напрямую.
В этой статье вы узнаете, как и когда использовать useRef, разберетесь с типовыми сценариями, увидите примеры кода и поймете, почему этот хук так важен для эффективной работы с React.
Что такое useRef
Основная идея
useRef — это хук, который позволяет вам создать ссылку (ref), хранящую изменяемое значение, не влияющее на рендеринг компонента. Главная "фишка" заключается в том, что значение, помещенное в useRef, остается неизменным между рендерами и не вызывает повторного рендера, если его изменить.
Синтаксис
Использовать useRef максимально просто. Вот основной синтаксис:
import { useRef } from 'react'
const myRef = useRef(initialValue)
initialValue
— это изначальное значение, которое вы хотите хранить в ref.myRef
— объект с единственным полем.current
, в котором и хранится значение.
useRef и "рефы" классовых компонентов
Если вы ранее работали с классовыми компонентами, то, возможно, использовали React.createRef(). useRef — это современный способ создавать рефы внутри функциональных компонентов.
useRef
- это React Hook, который позволяет создавать изменяемые переменные, сохраняющиеся между рендерами компонента. Он часто используется для доступа к DOM-элементам или для хранения значений, которые не вызывают повторный рендеринг компонента при изменении. Если вы хотите научиться использовать useRef
в React и узнаете о его различных применениях — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.
Для чего обычно используют useRef
1. Сохранение изменяемых данных между рендерами
В отличие от состояния (useState), изменение useRef не приводит к повторному рендеру компонента. Это удобно, если вы хотите сохранить какое-то значение, причём не требуете отображать его в интерфейсе.
Например, можно использовать useRef для хранения предыдущего значения пропса или состояния, чтобы сравнивать его со свежим.
import { useEffect, useRef } from 'react'
function Example({ value }) {
const prevValue = useRef()
useEffect(() => {
// сохраняем текущее значение в prevValue при каждом изменении value
prevValue.current = value
}, [value])
return (
<div>
<div>Текущее: {value}</div>
<div>Предыдущее: {prevValue.current}</div>
</div>
)
}
// prevValue.current не инициирует рендер, хранит "старое" значение value
2. Получение доступа к DOM-элементам
Часто useRef используют для создания ссылок на DOM-элементы, чтобы управлять ими напрямую. Например: ставить фокус на input, прокручивать контейнер и т.д.
Давайте покажу:
import { useRef } from 'react'
function FocusInput() {
const inputRef = useRef(null)
const handleClick = () => {
// Вызываем фокус на <input> через ref
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Фокус на поле ввода</button>
</div>
)
}
// inputRef.current — это DOM элемент input, на который можно повлиять напрямую
3. Хранение таймеров, идентификаторов и сторонних данных
useRef хорошо подходит для хранения таймеров или внешних идентификаторов, которые важно "не терять" между рендерами.
Посмотрите пример:
import { useRef, useEffect } from 'react'
function TimerExample() {
const intervalId = useRef()
useEffect(() => {
intervalId.current = setInterval(() => {
console.log('Tick')
}, 1000)
// Очистка при размонтировании
return () => {
clearInterval(intervalId.current)
}
}, [])
return <div>Таймер работает. Смотрите консоль.</div>
}
// intervalId.current всегда содержит актуальный id таймера
Как работает useRef
Структура объекта ref
Когда вы вызываете useRef, он возвращает объект с единственным свойством current. Это свойство может быть любым значением: числом, строкой, объектом, DOM-элементом, функцией — чем угодно.
const myRef = useRef(0)
console.log(myRef.current) // 0
myRef.current = 5
console.log(myRef.current) // 5
Обратите внимание: если вы измените значение myRef.current, компонент НЕ перерендерится!
Как происходит "сохранение" значения
Внутри React useRef просто-напросто привязывает созданный объект к вашему компоненту на все время жизни этого компонента. Даже если компонент перерисуется из-за других изменений — объект ref останется тем же самым.
Применение useRef: практические кейсы
Взаимодействие с DOM
Управление фокусом и прокруткой
Очень частое использование useRef — работа с полями ввода и прокруткой контейнеров.
Давайте сделаем небольшой пример:
import { useRef } from 'react'
function ScrollExample() {
const divRef = useRef(null)
const handleScroll = () => {
// Прокручиваем контейнер вниз на 100px
divRef.current.scrollTop += 100
}
return (
<div>
<div
ref={divRef}
style={{height: 100, overflow: 'auto', border: '1px solid gray'}}
>
{/* Длинный текст для скролла */}
<div style={{height: 400}}>Много текста...</div>
</div>
<button onClick={handleScroll}>Прокрутить вниз</button>
</div>
)
}
// divRef.current — это ссылка на DOM элемент div
Чтение значения поля ввода
Если, например, вы хотите читать значение input без состояния:
import { useRef } from 'react'
function ReadInput() {
const inputRef = useRef()
const handleAlert = () => {
alert(`Текущее значение: ${inputRef.current.value}`)
}
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleAlert}>Показать значение</button>
</>
)
}
Сохранение предудущих значений пропсов или состояния
useRef — отличный способ отслеживать истории изменений значений без необходимости хранить их в состоянии.
Посмотрите пример:
import { useEffect, useRef, useState } from 'react'
function PreviousStateExample() {
const [count, setCount] = useState(0)
const prevCount = useRef()
useEffect(() => {
prevCount.current = count
}, [count])
return (
<div>
<div>Текущее: {count}</div>
<div>Предыдущее: {prevCount.current}</div>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}
// prevCount всегда хранит значение count из прошлого рендера
Хранение "неотслеживаемых" данных
Есть случаи, когда требуется хранить данные, не зависящие от UI, например, время или сторонние значения.
import { useRef } from 'react'
function TimeLogger() {
const startTimeRef = useRef(Date.now())
const logElapsed = () => {
console.log('Прошло мс:', Date.now() - startTimeRef.current)
}
return (
<button onClick={logElapsed}>Показать время с момента загрузки</button>
)
}
// startTimeRef хранит время первого рендера компонента
В чем разница между useRef и useState
Многие часто путают эти два хука, поэтому важно четко понять, где их использовать.
useState:
- Сохраняет значение между рендерами.
- Изменение значения = rerender компонента.
- Используется для того, что отображается в UI.
useRef:
- Сохраняет значение между рендерами.
- Изменение значения НЕ приводит к rerender.
- Используется для хранения "сервисных" данных, недоступных пользователю напрямую.
Давайте сравним:
import { useState, useRef } from 'react'
function CompareRefAndState() {
const [count, setCount] = useState(0)
const ref = useRef(0)
const incrementBoth = () => {
setCount(count + 1)
ref.current += 1
}
return (
<div>
<div>Счетчик через state: {count}</div>
<div>Счетчик через ref: {ref.current}</div>
<button onClick={incrementBoth}>Увеличить оба</button>
</div>
)
}
// Изменение state вызовет rerender, ref - нет!
Ограничения и подводные камни useRef
Когда НЕЛЬЗЯ использовать useRef вместо состояния
- Если вам нужно, чтобы изменение значения приводило к обновлению UI, используйте useState.
- Если вы хотите реагировать на изменение значения в useRef с помощью useEffect, помните: изменение
.current
не вызовет запуск эффекта. Только состояния или пропсы работают как триггер.
Потенциальные ошибки
- Если вы используете useRef для хранения DOM-элементов, не забывайте, что на первом рендере ref ещё равен null — он заполнится только после маунта компонента.
- Будьте осторожны с мутациями сложных объектов, хранящихся в current. Если вы передадите ref.child = ..., это не trigger-ит обновления и не всегда ожидаемое поведение.
useRef + forwardRef: проброс ref во вложенные компоненты
Иногда нужно пробросить ref на дочерний компонент, чтобы, например, дать родителю управление над focus внутри инпута во вложенном компоненте.
React предоставляет функцию forwardRef для прокидывания ref:
import { forwardRef, useRef } from 'react'
// Обертка для передачи ref
const CustomInput = forwardRef((props, ref) => (
<input ref={ref} {...props} />
))
function App() {
const inputRef = useRef(null)
const setFocus = () => {
inputRef.current.focus()
}
return (
<>
<CustomInput ref={inputRef} />
<button onClick={setFocus}>Фокус на CustomInput</button>
</>
)
}
// ref успешно проброшен на реальный input во вложенном компоненте
Советы по использованию useRef
- Используйте useRef, когда не нужно рендерить UI при изменении значения.
- Не забывайте, что ref можно менять в любых хуках и функциях, в отличие от useState, который надо обновлять через setState.
- Если работаете с DOM-элементом, ref всегда будет null до момента маунта.
- Если требуется избежать race condition в асинхронных эффектах, используйте useRef для хранения “актуального статуса” компонента (например, mounted/not mounted).
Заключение
useRef — это эффективный инструмент для работы с неизменяемыми между рендерами значениями внутри функциональных компонентов. Он прекрасно подходит для хранения сервисных данных, которые не должны инициировать повторный рендер, и для доступа к DOM-элементам напрямую. В отличие от useState, изменение значения useRef не приводит к обновлению UI, что делает этот хук незаменимым в ряде ситуаций: при работе с таймерами, DOM, сохранении идентификаторов и промежуточных данных.
Знание тонкостей применения useRef помогает писать более эффективный, предсказуемый и "чистый" код в React.
useRef
предоставляет дополнительные возможности. Для создания сложных приложений требуется умение управлять состоянием и роутингом. Рассмотрите курс Основы React, React Router и Redux Toolkit для получения необходимых навыков. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.
Частозадаваемые технические вопросы по теме useRef и ответы на них
1. Как сбросить содержимое useRef в null/начальное значение?
Вы в любой момент можете установить current в нужное значение:
js
myRef.current = null
// или
myRef.current = initialValue
2. Как получить доступ к реальному DOM элементу внутри useRef, если у меня вложенные компоненты?
Используйте React.forwardRef для проброса рефа до нужного DOM-элемента во вложенном компоненте. Обёрните функциональный компонент с help-ом forwardRef и передайте ref на реальный элемент (input, div и др.).
3. Можно ли передавать функцию в initialValue useRef так же, как useState?
Нет, useRef не вызывает функцию, если вы передаёте ее в initialValue, а просто сохраняет ее как значение. Используйте, например, useRef(() => {})
если хотите сохранить функцию.
4. Как правильно типизировать useRef в TypeScript?
Указывайте тип current при вызове:
tsx
const inputRef = useRef<HTMLInputElement | null>(null)
5. Как реагировать на изменения current в useRef с помощью useEffect?
Нельзя напрямую отследить изменения current через useEffect — используйте состояния, если необходима такая реакция. Альтернатива — создайте useState и синхронизируйте их явно, если требуется запускать эффекты по изменению значения.
Постройте личный план изучения React до уровня Middle — бесплатно!
React — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по React
Лучшие курсы по теме

React и Redux Toolkit
Антон Ларичев
TypeScript с нуля
Антон Ларичев