Refs и DOM

Почему Refs — это не просто «ссылка на элемент»
Вы уже освоили основы React и теперь чувствуете, что Refs — это удобный способ достучаться до DOM напрямую. Но здесь кроется первая ловушка: многие разработчики воспринимают ref как магическую палочку для любого «низкоуровневого» доступа. На самом деле, если вы слишком часто тянетесь к ref, это сигнал: что-то не так с архитектурой компонента. Вы начнёте замечать, как компоненты становятся менее декларативными, а тесты — хрупкими.
Представьте: вы делаете анимацию, фокусируете поле, работаете с медиаэлементом. В этих случаях ref — ваш друг. Но когда вам нужно получить значение input для отправки формы — используйте управляемые компоненты, иначе вы потеряете контроль над потоком данных. Профессионалы смотрят на ref как на аварийный выход, а не на парадную дверь.
Типичные проблемы: что идёт не так
Первый камень преткновения — синхронизация. Вы создали ref через useRef, передали в DOM-элемент, но в момент первого рендера ref равен null. Это не баг, это особенность жизненного цикла. Если вы попытаетесь прочитать node.current сразу после инициализации, то ничего не получите — узел ещё не смонтирован. Такое поджидает новичков при работе с useEffect и ref одновременно.
Вторая проблема — утечка производительности. Если вы используете ref в условном рендеринге и каждый раз пересоздаёте его в корне компонента, при перерендерах теряется ссылка на предыдущий DOM-элемент. Это приводит к «морганию» фокуса или сбросу состояния анимации. Специалисты всегда проверяют, не меняется ли ref при изменении пропсов, и при необходимости фиксируют его в useRef с начальным значением null.
Нюанс с forwardRef: передаём ref глубже
Когда вы пишете собственный компонент-обёртку, возникает вопрос: как передать ref вниз, к конкретному DOM-узлу? forwardRef существует именно для этого, но у него есть неочевидный эффект. Если ваш компонент использует ref только для сторонней библиотеки (например, для кастомного скроллбара), не стоит форвардить ref на каждый элемент внутри — это делает API компонента размытым. Вместо этого создайте специальный интерфейс с методами scrollTo или focus, доступными через ref.
Ещё один подводный камень: при использовании forwardRef с TypeScript приходится явно типизировать и ref, и компонент. Часто видели код, где ref объявлен как any — так вы теряете все преимущества статической проверки. Потратьте 10 минут на аккуратную типизацию, и IDE будет подсказывать вам методы DOM-элемента, а не просто «Object».
Работа с несколькими элементами: коллекция ref
Бывает, нужно создать набор ref для списка элементов — например, для фокусировки на следующем поле формы при нажатии Enter. Простейший способ — хранить массив рефов в useRef, где каждый элемент массива — новый createRef() или null. Но внимание: если динамически удаляете элементы из списка, массив пересоздаётся, и ref для удалённого элемента должен быть правильно очищен, иначе React попытается вызвать метод у уже unmounted элемента.
Профессиональный приём: используйте callback ref. Вы передаёте функцию, которая вызывается при каждом изменении узла (mount/unmount). Она принимает DOM-узел и автоматически присваивает его вашей переменной. Это даёт гибкость — можно управлять созданием и удалением ref без массивов. Плюс callback ref корректно обрабатывает асинхронные обновления списка.
Проблема «запаздывающего» DOM
Вы обновляете state и тут же обращаетесь к DOM через ref, ожидая, что элемент уже изменился. Но React применяет изменения асинхронно — между setState и фактическим обновлением DOM проходит микротаск. Чтобы избежать чтения старого DOM, используйте useLayoutEffect: он выполняется синхронно до того, как браузер отрисует изменения. Этим приёмом пользуются специалисты для анимаций, позиционирования тултипов и работы с шириной/высотой флекс-контейнеров.
Если же вы просто читаете значения (например, scrollTop), и разница в один кадр не критична, используйте useEffect — это безопаснее и производительнее. Помните: лишняя синхронизация в useLayoutEffect может привести к подёргиваниям интерфейса.
Когда ref — зло, а когда — спасение
- Сосредоточьтесь на редких операциях: ref оправдан для управления фокусом, выделением текста, воспроизведением медиа (видео/аудио). Всё, что делается один раз за сессию пользователя.
- Избегайте ref для чтения значений input: используйте управляемые поля. Иначе вы потеряете реактивность и усложните тестирование — не сможете легко симулировать ввод.
- Не храните в ref данные, участвующие в рендере: если значение ref меняется, компонент не перерендеривается. Это приводит к рассинхрону UI и состояния — классический баг, который сложно отловить.
- Используйте ref для интеграции с не-React кодом: jQuery-плагины, кастомные тени, WebGL. React не может управлять элементами, рождёнными за его пределами, поэтому ref — единственный мост.
- Помните об очистке: если вы создали слушатель событий или интервал в ref, обязательно удаляйте их в эффекте очистки (функция return в useEffect), иначе возникнут утечки памяти.
Профессиональный чек-лист работы с DOM через ref
- Проверяйте null: перед обращением к
currentвсегда проверяйте условиеif (node.current). Это убережёт от ошибок в SSR и при динамическом монтировании. - Типизируйте ref: используйте
useRef<HTMLVideoElement | null>(null)— так TypeScript подскажет все методы видеоэлемента, включаяplay,pause,requestPictureInPicture. - Избегайте ref-инъекций: не передавайте ref из родительского компонента в скрытые элементы — это нарушает инкапсуляцию.
- Оптимизируйте количество ref: один ref на компонент — оптимально. Если нужно много ссылок, рассмотрите рефакторинг или разделение компонента.
- Используйте callback ref для анимаций: он позволяет выполнить код сразу после вставки элемента в DOM — идеально для измерения размеров.
- Документируйте ref: добавьте JSDoc к пропсу ref, если компонент публичный — другие разработчики будут благодарны за подсказки.
Как отучить себя злоупотреблять ref
Часто причиной использования ref становится простое незнание альтернатив. Например, чтобы изменить класс элемента по клику, вы тянетесь к element.classList через ref. Вместо этого добавьте в state флаг и используйте условный класс в JSX. Это сохранит паттерн «сверху вниз» и упростит отладку. Специалисты советуют: перед тем как взять ref, напишите ответ на вопрос «Могу ли я этого добиться через props?».
И ещё один жёсткий приём: ограничьте себя одним ref на компонент. Если не помещается — значит, компонент делает слишком много. Разбейте его на дочерние, передавайте состояние через props. В результате вы получите более переиспользуемый код, который легче тестировать и поддерживать.
В итоге, когда вы перестанете рассматривать ref как «просто ссылку» и начнёте видеть в нём инструмент с чёткими границами применения, ваш код станет чище и надёжнее. Вы научитесь отличать ситуации, где ref — это правильный выбор, от тех, где он маскирует плохую архитектуру. И запомните: профессиональный React-разработчик знает, что 80% случаев использования ref можно заменить лучшим решением. А оставшиеся 20% — это именно то, где ref незаменим.
Добавлено: 23.04.2026
