Refs — прямой доступ к DOM и хранение данных без рендера
Содержание
- Введение: что такое Refs и зачем они нужны
- Основы использования
useRef - Управление DOM-элементами через Refs
- Refs для работы с медиа-элементами
- Refs как mutable-контейнер (без перерендера)
- Хранение предыдущих значений
- Refs для callback-функций (Callback Refs)
- Refs в классовых компонентах (для понимания)
- Forwarding Refs (передача Ref в дочерний компонент)
useImperativeHandle— кастомные методы через Refs- Refs и анимации
- Refs vs State (сравнение)
- Частые ошибки новичков
- Итог (что мы узнали)
- Задание для закрепления (на 1–2 часа)
- Что дальше?
Цель урока: научиться использовать Refs для прямого доступа к DOM-элементам, управления фокусом, анимациями, интеграции со сторонними библиотеками и хранения mutable-значений, которые не вызывают перерендер компонента.
Введение: что такое Refs и зачем они нужны
Главная мысль:
Ref — это как «кабель», который соединяет React-компонент с реальным DOM-элементом. Через него можно делать то, что невозможно сделать через React-подход.
Когда нужны Refs:
- Управление фокусом, выделением текста, медиа-плеерами
- Запуск анимаций (не через React-состояние)
- Интеграция со сторонними DOM-библиотеками
- Хранение предыдущих значений
- Хранение значений, которые не должны вызывать перерендер
Когда НЕ нужны Refs:
- Для данных, которые должны обновлять интерфейс (используй
state) - Вместо управления состоянием формы (используй
stateили библиотеки)
Основы использования useRef
Синтаксис:
const ref = useRef(initialValue);
// ref.current — доступ к значению
Простейший пример: фокус на поле
import { useRef, useEffect } from 'react';
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// После монтирования ставим фокус на поле
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Введите текст..." />;
}
Что здесь происходит:
useRef(null)создаёт объект{ current: null }ref={inputRef}связывает DOM-элемент с этим объектом- После монтирования
inputRef.currentуказывает на реальный DOM-элемент<input> - Вызываем метод
focus()напрямую
Управление DOM-элементами через Refs
Примеры с разными методами:
function InputControls() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
const blurInput = () => {
inputRef.current.blur();
};
const selectText = () => {
inputRef.current.select();
};
const getValue = () => {
alert(inputRef.current.value);
};
return (
<div>
<input ref={inputRef} defaultValue="Пример текста" />
<button onClick={focusInput}>Фокус</button>
<button onClick={blurInput}>Убрать фокус</button>
<button onClick={selectText}>Выделить текст</button>
<button onClick={getValue}>Показать значение</button>
</div>
);
}
Refs для работы с медиа-элементами
function VideoPlayer({ src }) {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const play = () => {
videoRef.current.play();
setIsPlaying(true);
};
const pause = () => {
videoRef.current.pause();
setIsPlaying(false);
};
const togglePlay = () => {
if (isPlaying) {
pause();
} else {
play();
}
};
const restart = () => {
videoRef.current.currentTime = 0;
videoRef.current.play();
};
return (
<div>
<video
ref={videoRef}
src={src}
width="400"
controls={false}
/>
<div>
<button onClick={togglePlay}>
{isPlaying ? '⏸️ Пауза' : '▶️ Воспроизвести'}
</button>
<button onClick={restart}>⏪ С начала</button>
</div>
</div>
);
}
Оживи свой сайт. Освой JavaScript!
Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.
От теории — к реальным скриптам в твоём портфолио.
Refs как mutable-контейнер (без перерендера)
Основное свойство: изменение ref.current НЕ вызывает перерендер компонента.
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const [isRunning, setIsRunning] = useState(false);
const startTimer = () => {
if (intervalRef.current) return; // Уже запущен
intervalRef.current = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
setIsRunning(true);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
setIsRunning(false);
};
const resetTimer = () => {
stopTimer();
setCount(0);
};
return (
<div>
<p>Секунд: {count}</p>
<button onClick={startTimer} disabled={isRunning}>Старт</button>
<button onClick={stopTimer} disabled={!isRunning}>Стоп</button>
<button onClick={resetTimer}>Сброс</button>
</div>
);
}
Почему это работает:
intervalRefхранит ID таймера- Изменение
intervalRef.currentне вызывает перерендер - Таймер продолжает работать, даже если компонент перерендерится
Хранение предыдущих значений
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value; // Обновляем после рендера
}, [value]);
return ref.current; // Возвращаем предыдущее значение
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Сейчас: {count}</p>
<p>Было: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Refs для callback-функций (Callback Refs)
Кроме useRef, можно использовать callback-функцию:
function MeasuredComponent() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback((node) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
<p ref={measuredRef}>Измеряемый текст</p>
<p>Высота элемента: {height}px</p>
</div>
);
}
Когда использовать:
- Нужно измерить DOM-элемент
- Нужно выполнить действие при монтировании/размонтировании элемента
- Работа с динамическими списками элементов
Refs в классовых компонентах (для понимания)
class InputFocus extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return <input ref={this.inputRef} />;
}
}
Forwarding Refs (передача Ref в дочерний компонент)
Иногда нужно передать ref в дочерний компонент:
import { forwardRef, useRef } from 'react';
// Дочерний компонент, который принимает ref
const FancyInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
style={{ border: '2px solid blue', padding: '8px' }}
{...props}
/>
);
});
// Родительский компонент
function Parent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<FancyInput ref={inputRef} placeholder="Напиши что-нибудь" />
<button onClick={focusInput}>Фокус</button>
</div>
);
}
Когда нужно:
- Библиотеки компонентов (Material-UI, Ant Design)
- Сложные компоненты, скрывающие DOM-структуру
- Интеграция с сторонними библиотеками
useImperativeHandle — кастомные методы через Refs
Позволяет создать свои методы, доступные через ref:
import { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// Определяем, какие методы будут доступны через ref
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
shake: () => {
const input = inputRef.current;
input.style.transform = 'translateX(-10px)';
setTimeout(() => {
input.style.transform = 'translateX(10px)';
setTimeout(() => {
input.style.transform = 'translateX(0)';
}, 100);
}, 100);
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} {...props} />;
});
function Parent() {
const inputRef = useRef(null);
const handleShake = () => {
inputRef.current.shake();
};
const handleGetValue = () => {
alert(inputRef.current.getValue());
};
return (
<div>
<CustomInput ref={inputRef} placeholder="Введите текст" />
<button onClick={handleShake}>Тряхнуть</button>
<button onClick={handleGetValue}>Показать значение</button>
</div>
);
}
Когда использовать:
- Создание собственных библиотек компонентов
- Сокрытие сложной логики внутри компонента
- Предоставление удобного API для разработчиков
Refs и анимации
function AnimatedBox() {
const boxRef = useRef(null);
const [isAnimating, setIsAnimating] = useState(false);
const animateBox = () => {
const box = boxRef.current;
if (!box) return;
setIsAnimating(true);
// Анимация через CSS-трансформации
box.style.transition = 'transform 0.5s ease';
box.style.transform = 'translateX(100px) rotate(45deg)';
setTimeout(() => {
box.style.transform = 'translateX(0) rotate(0deg)';
setTimeout(() => {
setIsAnimating(false);
}, 500);
}, 500);
};
return (
<div>
<div
ref={boxRef}
style={{
width: '100px',
height: '100px',
background: 'blue',
margin: '20px 0'
}}
/>
<button onClick={animateBox} disabled={isAnimating}>
{isAnimating ? 'Анимация...' : 'Анимировать'}
</button>
</div>
);
}
Refs vs State (сравнение)
| Характеристика | Refs (useRef) |
State (useState) |
|---|---|---|
| Изменение вызывает перерендер? | ❌ Нет | ✅ Да |
| Хранит данные между рендерами? | ✅ Да | ✅ Да |
| Доступ к DOM-элементам? | ✅ Да | ❌ Нет |
| Может управлять интерфейсом? | ❌ Нет | ✅ Да |
| Подходит для таймеров? | ✅ Да | ⚠️ Только с осторожностью |
| Где хранить предыдущее значение? | ✅ Да | ❌ Нет |
Частые ошибки новичков
| Ошибка | Почему | Исправление |
|---|---|---|
ref.current равен null |
Элемент ещё не смонтировался | Используй useEffect или проверку |
Меняют ref в useEffect без проверки |
Может вызвать ошибку | Проверяй if (ref.current) |
Используют ref для состояния интерфейса |
Не вызывает перерендер | Используй useState |
Не используют forwardRef |
ref не доходит до DOM-элемента |
Оберни компонент в forwardRef |
Изменяют ref напрямую в JSX |
Неправильный синтаксис | Правильно: ref={myRef} |
Итог (что мы узнали)
| Что узнали | Как это работает |
|---|---|
useRef |
Создание ссылки на DOM-элемент или хранилище |
| DOM-доступ | Прямые методы: focus, blur, play, pause |
mutable-контейнер |
Хранение данных без перерендера |
usePrevious |
Хранение предыдущего значения |
forwardRef |
Передача ref в дочерние компоненты |
useImperativeHandle |
Создание кастомных методов через ref |
| Refs vs State | Когда что использовать |
Задание для закрепления (на 1–2 часа)
Создай компонент MusicPlayer, который:
- Использует
useRefдля доступа к<audio>элементу - Имеет кнопки: ▶️ Воспроизвести, ⏸️ Пауза, ⏹️ Остановить, ⏪ Перемотать на 10 секунд назад
- Показывает текущее время трека через state (обновляется каждую секунду через
setInterval) - Использует
useRefдля храненияintervalId(чтобы очищать при размонтировании) - В useEffect добавляет обработчик события
ended(когда трек закончился — автоматически перематывает на начало)
Дополнительно: Создай второй компонент AutoFocusForm:
- При монтировании устанавливает фокус на первое поле
- При сабмите формы переключает фокус на следующее поле (через refs)
Проверка: убедись, что плеер работает, а форма переключает фокус между полями.
Что дальше?
В следующем уроке разберём Render Props — паттерн, который позволяет делиться кодом между компонентами через функцию-пропс. Ты узнаешь, как создавать гибкие компоненты, которые могут рендерить разный контент в зависимости от переданной функции.