Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Работа с Ref в React - обновление и лучшие практики
Ref - это мощный инструмент в React для прямого доступа к элементам DOM. Понимание того, как правильно обновлять и использовать ref, критично для опытного разработчика.
1. useRef и изменение значения
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
// Обновляем ref через присвоение
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Сфокусировать</button>
</>
);
}
2. Обновление значения ref
function Counter() {
const countRef = useRef<number>(0);
const increment = () => {
// Обновляем значение ref напрямую
countRef.current += 1;
console.log(`Count: ${countRef.current}`);
// Важно: это НЕ вызывает re-render!
};
const handleClick = () => {
increment();
// Компонент не перерендерится
};
return (
<>
<p>Count in ref: {countRef.current}</p>
<button onClick={handleClick}>Увеличить</button>
</>
);
}
3. Ref с useEffect
function SearchInput() {
const inputRef = useRef<HTMLInputElement>(null);
const [query, setQuery] = useState('');
// Обновляем DOM элемент через ref
useEffect(() => {
if (inputRef.current) {
// Очищаем input
inputRef.current.value = '';
// Устанавливаем фокус
inputRef.current.focus();
// Устанавливаем placeholder
inputRef.current.placeholder = 'Новый placeholder';
}
}, []);
return <input ref={inputRef} onChange={(e) => setQuery(e.target.value)} />;
}
4. useImperativeHandle - раскрытие методов компонента
// Child компонент
interface InputHandle {
focus: () => void;
clear: () => void;
getValue: () => string;
}
const Input = forwardRef<InputHandle, { placeholder: string }>(
({ placeholder }, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
// Определяем методы, которые родитель может вызвать
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
clear: () => {
if (inputRef.current) {
inputRef.current.value = '';
}
},
getValue: () => {
return inputRef.current?.value || '';
},
}));
return <input ref={inputRef} placeholder={placeholder} />;
}
);
// Parent компонент
function Form() {
const inputRef = useRef<InputHandle>(null);
const handleSubmit = () => {
const value = inputRef.current?.getValue();
console.log('Form value:', value);
};
const handleClear = () => {
inputRef.current?.clear();
};
return (
<>
<Input ref={inputRef} placeholder="Введите текст" />
<button onClick={handleClear}>Очистить</button>
<button onClick={handleSubmit}>Отправить</button>
</>
);
}
5. Обновление ref внутри callback'а
function VideoPlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
// Обновляем состояние через ref в callback
const handlePlayPause = useCallback(() => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
}, [isPlaying]);
const handleSetTime = useCallback((time: number) => {
if (videoRef.current) {
videoRef.current.currentTime = time;
}
}, []);
return (
<>
<video ref={videoRef} src="video.mp4" />
<button onClick={handlePlayPause}>
{isPlaying ? 'Пауза' : 'Воспроизведение'}
</button>
<button onClick={() => handleSetTime(10)}>Перейти к 10s</button>
</>
);
}
6. Работа с несколькими ref
function FormWithMultipleInputs() {
const firstNameRef = useRef<HTMLInputElement>(null);
const lastNameRef = useRef<HTMLInputElement>(null);
const emailRef = useRef<HTMLInputElement>(null);
const handleFocusNext = (current: React.RefObject<HTMLInputElement>) => {
// Идем к следующему input
const allInputs = [
{ ref: firstNameRef, name: 'firstName' },
{ ref: lastNameRef, name: 'lastName' },
{ ref: emailRef, name: 'email' },
];
const currentIndex = allInputs.findIndex((input) => input.ref === current);
const nextInput = allInputs[currentIndex + 1];
nextInput?.ref.current?.focus();
};
return (
<>
<input
ref={firstNameRef}
type="text"
placeholder="Имя"
onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(firstNameRef)}
/>
<input
ref={lastNameRef}
type="text"
placeholder="Фамилия"
onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(lastNameRef)}
/>
<input
ref={emailRef}
type="email"
placeholder="Email"
onKeyPress={(e) => e.key === 'Enter' && handleFocusNext(emailRef)}
/>
</>
);
}
7. Ref и useState - когда что использовать
// Используй useState если нужен re-render
function Counter1() {
const [count, setCount] = useState(0);
// Обновление count -> re-render компонента
return <div>{count}</div>;
}
// Используй useRef если re-render не нужен
function Stopwatch() {
const timeRef = useRef<number>(0);
const handleStart = () => {
// Обновляем timeRef без re-render
timeRef.current = Date.now();
};
// Компонент не перерендерится
return <button onClick={handleStart}>Старт</button>;
}
// Комбинируй оба подхода
function Form() {
const [formData, setFormData] = useState({ name: '', email: '' });
const submitCountRef = useRef<number>(0);
const handleSubmit = () => {
submitCountRef.current += 1; // Не вызывает re-render
console.log(`Попыток отправки: ${submitCountRef.current}`);
setFormData({ name: '', email: '' }); // Вызывает re-render
};
return (
<>
<input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
<button onClick={handleSubmit}>Отправить</button>
</>
);
}
8. Обновление ref в custom hook
function useTimer(initialTime: number = 0) {
const timeRef = useRef<number>(initialTime);
const intervalRef = useRef<number | null>(null);
const [time, setTime] = useState(initialTime);
const start = useCallback(() => {
if (intervalRef.current === null) {
intervalRef.current = window.setInterval(() => {
timeRef.current += 1;
setTime(timeRef.current);
}, 1000);
}
}, []);
const stop = useCallback(() => {
if (intervalRef.current !== null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
const reset = useCallback(() => {
stop();
timeRef.current = initialTime;
setTime(initialTime);
}, [initialTime, stop]);
return { time, start, stop, reset };
}
// Использование
function Stopwatch() {
const { time, start, stop, reset } = useTimer(0);
return (
<>
<p>Время: {time}s</p>
<button onClick={start}>Старт</button>
<button onClick={stop}>Стоп</button>
<button onClick={reset}>Сброс</button>
</>
);
}
9. Обновление ref с помощью Object.assign
function ComplexComponent() {
const dataRef = useRef<{ name: string; count: number }>({
name: 'Initial',
count: 0,
});
const updateRef = (updates: Partial<typeof dataRef.current>) => {
// Обновляем объект в ref
Object.assign(dataRef.current, updates);
console.log(dataRef.current);
};
return (
<>
<button onClick={() => updateRef({ name: 'Updated' })}>
Update Name
</button>
<button onClick={() => updateRef({ count: dataRef.current.count + 1 })}>
Increment Count
</button>
</>
);
}
Важные правила
- Не читай ref в render методе - используй useState если нужны updates в UI
- Не присваивай ref вне ref callback'а - используй useRef или forwardRef
- Обновляй ref через .current - это основной способ
- Очищай ref в useEffect cleanup - для предотвращения утечек памяти
- Используй useImperativeHandle для API компонента - если компонент нужно "контролировать" снаружи
Когда использовать Ref
- Управление фокусом на input'ах
- Триггеринг анимаций
- Интеграция с third-party DOM библиотеками
- Управление медиа элементами (video, audio)
- Хранение таймеров и интервалов
Когда НЕ использовать Ref
- Для управления состоянием - используй useState
- Для обновления UI - используй setState
- Когда можно решить через props