← Назад к вопросам
Как передать ref в компонент реализованный как функция?
2.0 Middle🔥 162 комментариев
#React
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как передать ref в компонент реализованный как функция?
Передача ref в функциональные компоненты - частый вопрос в React. По умолчанию вы не можете просто передать ref через пропсы функциональному компоненту, так как это специальное свойство, которое обрабатывает React. Для этого используется React.forwardRef.
Базовый подход с forwardRef
import { forwardRef, Ref } from "react";
interface InputProps {
placeholder?: string;
onChange?: (value: string) => void;
}
// Без forwardRef
const InputWithoutRef = (props: InputProps) => {
return <input type="text" {...props} />;
};
// С forwardRef - правильно
const Input = forwardRef<HTMLInputElement, InputProps>(
({ placeholder, onChange }, ref) => {
return (
<input
ref={ref}
type="text"
placeholder={placeholder}
onChange={(e) => onChange?.(e.target.value)}
/>
);
}
);
Input.displayName = "Input"; // Для DevTools
// Использование
function App() {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<>
<Input ref={inputRef} placeholder="Type here" />
<button onClick={handleClick}>Focus Input</button>
</>
);
}
Объяснение forwardRef
forwardRef - это wrapper, который позволяет компоненту получить ref в качестве второго аргумента после props. Без него ref игнорируется.
Типы в TypeScript:
// forwardRef<RefType, PropsType>
const Component = forwardRef<HTMLDivElement, MyComponentProps>(
(props, ref) => {
return <div ref={ref}>{props.children}</div>;
}
);
Передача ref к нативному элементу
Мост между пропсами и ref:
interface CardProps {
title: string;
children: React.ReactNode;
className?: string;
}
const Card = forwardRef<HTMLDivElement, CardProps>(
({ title, children, className }, ref) => {
return (
<div ref={ref} className={`card ${className || ""}`}>
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
);
Card.displayName = "Card";
// Использование
function App() {
const cardRef = useRef<HTMLDivElement>(null);
return (
<Card ref={cardRef} title="My Card">
Content here
</Card>
);
}
Кастомный ref с useImperativeHandle
Если вы хотите контролировать то, что доступно через ref, используйте useImperativeHandle:
interface InputHandle {
focus: () => void;
clear: () => void;
getValue: () => string;
}
const Input = forwardRef<InputHandle, InputProps>(
({ placeholder }, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) {
inputRef.current.value = "";
}
},
getValue: () => inputRef.current?.value || "",
}));
return <input ref={inputRef} type="text" placeholder={placeholder} />;
}
);
Input.displayName = "Input";
// Использование
function App() {
const inputRef = useRef<InputHandle>(null);
return (
<>
<Input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>
Focus
</button>
<button onClick={() => inputRef.current?.clear()}>
Clear
</button>
<button onClick={() => console.log(inputRef.current?.getValue())}>
Get Value
</button>
</>
);
}
Типизация useImperativeHandle
// Сначала определяем интерфейс методов
interface TextEditorHandle {
insertText: (text: string) => void;
getText: () => string;
clear: () => void;
}
// Потом в forwardRef указываем этот интерфейс
const TextEditor = forwardRef<TextEditorHandle, EditorProps>(
({ initialValue }, ref) => {
const textRef = useRef<HTMLTextAreaElement>(null);
useImperativeHandle(ref, () => ({
insertText: (text) => {
if (textRef.current) {
textRef.current.value += text;
}
},
getText: () => textRef.current?.value || "",
clear: () => {
if (textRef.current) {
textRef.current.value = "";
}
},
}), []);
return <textarea ref={textRef} defaultValue={initialValue} />;
}
);
TextEditor.displayName = "TextEditor";
Распространение ref к вложенным компонентам
const Dialog = forwardRef<HTMLDialogElement, DialogProps>(
({ children, title }, ref) => {
return (
<dialog ref={ref}>
<h2>{title}</h2>
{children}
</dialog>
);
}
);
Dialog.displayName = "Dialog";
// Использование
function App() {
const dialogRef = useRef<HTMLDialogElement>(null);
return (
<>
<Dialog ref={dialogRef} title="Confirm">
Are you sure?
</Dialog>
<button onClick={() => dialogRef.current?.showModal()}>
Open Dialog
</button>
</>
);
}
Важные замечания
- displayName - всегда добавляйте для DevTools
- Dependency array в useImperativeHandle - если пуст [], то методы никогда не пересчитываются
- Нельзя передать ref на функцию - только на нативные DOM элементы или компоненты с forwardRef
- forwardRef нарушает PureComponent и React.memo - используйте memo(Component, isEqual)
// Правильно с memo
const MemoizedInput = memo(Input);
// Если нужен и memo, и forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
export default memo(Input, (prev, next) => {
return prev.placeholder === next.placeholder;
});
Когда использовать ref
Ref нужны редко. Вот примеры когда они оправданы:
- Управление фокусом (input.focus())
- Срабатывание анимаций
- Интеграция с third-party DOM библиотеками
- Получение значений из нативных элементов
Для всего остального - используйте state и props.
Итоговый шаблон
interface CustomComponentHandle {
method1: () => void;
method2: (arg: string) => string;
}
const CustomComponent = forwardRef<CustomComponentHandle, Props>(
(props, ref) => {
const internalRef = useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({
method1: () => { /* logic */ },
method2: (arg) => { return arg; },
}), []);
return <div ref={internalRef}>{props.children}</div>;
}
);
CustomComponent.displayName = "CustomComponent";