Что такое React.forwardRef в React?
React.forwardRef
По умолчанию атрибут ref не является обычным пропом: React обрабатывает его особым образом и не передаёт внутрь компонента. Если родитель хочет получить ссылку на DOM-узел внутри дочернего компонента, необходимо явно «пробросить» ref с помощью React.forwardRef.
Синтаксис
React.forwardRef принимает функцию рендера с двумя аргументами — props и ref — и возвращает React-компонент:
const MyInput = React.forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
Теперь родитель может создать ref и получить прямой доступ к DOM-элементу <input>:
const ref = useRef<HTMLInputElement>(null);
<MyInput ref={ref} placeholder="Введите текст" />;
// ref.current — это сам <input>
Зачем это нужно
ForwardRef решает задачи, где родителю нужен императивный доступ к внутреннему узлу:
- программный фокус (
ref.current.focus()) - измерение размеров и позиции элемента
- интеграция со сторонними библиотеками, работающими с DOM напрямую
- анимации через GSAP, Framer Motion и т.п.
Комбинация с useImperativeHandle
Часто forwardRef используют вместе с useImperativeHandle, чтобы не отдавать родителю весь DOM-узел, а экспортировать только нужный API:
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
// Родитель получит только метод focus, а не весь DOM-элемент
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} {...props} />;
});
Родитель работает с безопасным интерфейсом:
type FancyInputHandle = { focus: () => void; clear: () => void };
const ref = useRef<FancyInputHandle>(null);
<FancyInput ref={ref} />;
ref.current?.focus();
ref.current?.clear();
displayName для отладки
При использовании forwardRef компонент в DevTools отображается как ForwardRef. Чтобы дать ему читаемое имя, установите displayName:
const MyInput = React.forwardRef((props, ref) => <input ref={ref} {...props} />);
MyInput.displayName = 'MyInput';
React 19 и новый подход
Начиная с React 19, ref передаётся как обычный проп и forwardRef больше не нужен для новых компонентов. Однако в проектах на React 16–18 forwardRef остаётся стандартным решением и встречается повсеместно.
Когда НЕ нужен forwardRef
- Если родитель не нуждается в доступе к DOM — не добавляйте forwardRef без причины.
- Для передачи данных между компонентами используйте пропы, контекст или стейт-менеджер, а не ref.
Что хочет услышать интервьюер
Понимание того, почему ref не является обычным пропом и не передаётся автоматически
Знание синтаксиса React.forwardRef и аргументов функции рендера (props, ref)
Умение объяснить практические сценарии применения: фокус, анимации, интеграция с DOM-библиотеками
Знание useImperativeHandle как способа ограничить экспортируемый API компонента
Осведомлённость об изменениях в React 19, где ref стал обычным пропом
Пример: Базовый проброс ref в DOM-элемент
import React, { useRef } from 'react';
// Компонент принимает ref и кидает его прямо на <input>
const TextInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
(props, ref) => {
return <input ref={ref} className="text-input" {...props} />;
}
);
TextInput.displayName = 'TextInput';
// Использование в родителе
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
// Прямой доступ к DOM-узлу
inputRef.current?.focus();
};
return (
<div>
<TextInput ref={inputRef} placeholder="Имя" />
<button onClick={handleFocus}>Сфокусировать</button>
</div>
);
}
Пример: forwardRef + useImperativeHandle — безопасный API
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// Описываем только те методы, которые хотим отдать родителю
type VideoPlayerHandle = {
play: () => void;
pause: () => void;
seek: (seconds: number) => void;
};
const VideoPlayer = forwardRef<VideoPlayerHandle, { src: string }>(
({ src }, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
useImperativeHandle(ref, () => ({
play: () => videoRef.current?.play(),
pause: () => videoRef.current?.pause(),
// Родитель не может напрямую менять volume, currentTime и т.д.
seek: (seconds) => {
if (videoRef.current) videoRef.current.currentTime = seconds;
},
}));
return <video ref={videoRef} src={src} />;
}
);
VideoPlayer.displayName = 'VideoPlayer';
// Родитель управляет плеером через ограниченный интерфейс
function App() {
const playerRef = useRef<VideoPlayerHandle>(null);
return (
<>
<VideoPlayer ref={playerRef} src="/video.mp4" />
<button onClick={() => playerRef.current?.play()}>Play</button>
<button onClick={() => playerRef.current?.seek(30)}>+30 сек</button>
</>
);
}
Типичные ошибки
Попытка использовать ref-атрибут на функциональном компоненте без forwardRef и непонимание, почему ref равен null
Забывают установить displayName, что затрудняет отладку в React DevTools
Путают forwardRef с useImperativeHandle — объясняют их как взаимозаменяемые, хотя они решают разные задачи
Пробрасывают ref на корневой DOM-узел, когда нужно было скрыть детали реализации через useImperativeHandle
Не знают, что в React 19 forwardRef устарел и ref теперь передаётся как обычный проп


