← Назад к вопросам
Как useRef используется с useImperativeHandle?
2.0 Middle🔥 221 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
useRef и useImperativeHandle в React
Основные концепции
useRef — хук для создания мутабельного объекта, который живёт столько же, сколько компонент. Обычно используется для доступа к DOM элементам.
useImperativeHandle — хук для экспортирования функций/значений из компонента наружу, сделав компонент "управляемым" извне.
Вместе они позволяют родителю вызывать методы на дочернем компоненте.
Простой пример: Фокус на input
// Дочерний компонент
const TextInput = React.forwardRef((props, ref) => {
const inputRef = useRef(null);
// Экспортируем метод focus для использования родителем
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = "";
},
}));
return <input ref={inputRef} type="text" />;
});
// Родительский компонент
function App() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus(); // Вызовем метод из дочернего компонента
};
const handleClear = () => {
inputRef.current.clear();
};
return (
<div>
<TextInput ref={inputRef} />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleClear}>Clear Input</button>
</div>
);
}
Ключевые компоненты
1. forwardRef (передача ref в дочерний компонент)
By default, refs не передаются как props. Нужно использовать forwardRef:
// Без forwardRef — ref не будет работать
function MyInput(props) {
return <input />; // props.ref будет undefined
}
// С forwardRef — ref передаётся вторым аргументом
const MyInput = forwardRef((props, ref) => {
return <input ref={ref} />;
});
2. useRef (создание изменяемого объекта)
const ref = useRef(initialValue);
// ref — это объект с одним свойством .current
console.log(ref.current); // может быть DOM элемент, число, объект и т.д.
// useRef НЕ запускает ре-рендер при изменении
ref.current = "new value"; // не вызовет re-render
3. useImperativeHandle (экспорт API)
useImperativeHandle(ref, createHandle, dependencies);
// ref — ref от родителя
// createHandle — функция, которая возвращает объект с методами
// dependencies — зависимости (как в useEffect)
useImperativeHandle(
ref,
() => ({
// методы и значения для родителя
focus: () => { /* ... */ },
value: () => { /* ... */ },
reset: () => { /* ... */ },
}),
[] // пересчитать, если что-то из зависимостей изменилось
);
Практический пример: видеоплеер
const VideoPlayer = forwardRef(({ src }, ref) => {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
useImperativeHandle(ref, () => ({
play: () => {
videoRef.current.play();
setIsPlaying(true);
},
pause: () => {
videoRef.current.pause();
setIsPlaying(false);
},
getCurrentTime: () => videoRef.current.currentTime,
setCurrentTime: (time) => {
videoRef.current.currentTime = time;
},
getDuration: () => videoRef.current.duration,
isPlaying: () => isPlaying,
}));
return (
<video
ref={videoRef}
src={src}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
);
});
// Использование
function MovieApp() {
const playerRef = useRef(null);
return (
<div>
<VideoPlayer ref={playerRef} src="movie.mp4" />
<button onClick={() => playerRef.current.play()}>Play</button>
<button onClick={() => playerRef.current.pause()}>Pause</button>
<button onClick={() => alert(playerRef.current.getCurrentTime())}>Show Time</button>
</div>
);
}
Пример: модальное окно
const Modal = forwardRef(({ children }, ref) => {
const [isOpen, setIsOpen] = useState(false);
const dialogRef = useRef(null);
useImperativeHandle(ref, () => ({
open: () => {
setIsOpen(true);
dialogRef.current?.showModal();
},
close: () => {
setIsOpen(false);
dialogRef.current?.close();
},
isOpen: () => isOpen,
}));
return (
<dialog ref={dialogRef}>
{children}
<button onClick={() => ref.current.close()}>Close</button>
</dialog>
);
});
// Использование
function App() {
const modalRef = useRef(null);
return (
<div>
<button onClick={() => modalRef.current?.open()}>Open Modal</button>
<Modal ref={modalRef}>
<h2>Important Message</h2>
<p>This is a modal dialog</p>
</Modal>
</div>
);
}
Пример: форма с валидацией
const Form = forwardRef(({ onSubmit }, ref) => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const [errors, setErrors] = useState({});
useImperativeHandle(ref, () => ({
validate: () => {
const newErrors = {};
if (!emailRef.current.value.includes("@")) {
newErrors.email = "Invalid email";
}
if (passwordRef.current.value.length < 6) {
newErrors.password = "Password too short";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
},
getValues: () => ({
email: emailRef.current.value,
password: passwordRef.current.value,
}),
reset: () => {
emailRef.current.value = "";
passwordRef.current.value = "";
setErrors({});
},
}));
return (
<form onSubmit={(e) => {
e.preventDefault();
if (ref.current.validate()) {
onSubmit(ref.current.getValues());
}
}}>
<input ref={emailRef} type="email" placeholder="Email" />
{errors.email && <span>{errors.email}</span>}
<input ref={passwordRef} type="password" placeholder="Password" />
{errors.password && <span>{errors.password}</span>}
<button type="submit">Sign In</button>
</form>
);
});
// Использование
function LoginPage() {
const formRef = useRef(null);
const handleLogin = async (credentials) => {
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify(credentials),
});
if (response.ok) {
formRef.current?.reset();
}
};
return <Form ref={formRef} onSubmit={handleLogin} />;
}
Когда НЕ использовать useImperativeHandle
// Плохо: использовать для prop-based управления
const BadComponent = forwardRef(({ isOpen }, ref) => {
useImperativeHandle(ref, () => ({
open: () => { /* ... */ }, // Зачем? isOpen prop уже это делает
}));
});
// Хорошо: пропс для такого случая
const GoodComponent = ({ isOpen, onClose }) => {
// Используй pропсы для управления состоянием
};
Лучшие практики
- Используй только когда нужно управлять DOM — звоните по методам на компонентах, это не React way
- Ограничивай API — экспортируй только необходимые методы
- Типизируй с TypeScript:
interface TextInputHandle {
focus: () => void;
clear: () => void;
}
const TextInput = forwardRef<TextInputHandle>((props, ref) => {
// ...
});
- Избегай abuse — если можно решить через props и state, решай через них
- Документируй экспортируемый API — какие методы доступны, что они делают
Выводы: useImperativeHandle — мощный инструмент для создания управляемых компонентов, но использовать его нужно осторожно. Обычно достаточно props и state.