Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
useRef и взаимодействие с DOM в React
useRef — это React хук, который позволяет прямо обращаться к DOM-элементам и сохранять изменяемые значения, которые не вызывают перерендер при изменении. Это мост между React-абстракцией и браузерным DOM.
Основное назначение
useRef используется для:
- Прямого доступа к DOM (фокус, выбор текста, запуск анимации)
- Сохранения значений между рендерами без вызова перерендера
- Хранения идентификаторов таймеров для очистки
- Хранения предыдущих значений (previous props/state)
Как useRef взаимодействует с DOM
1. Создание ссылки на элемент
import { useRef } from 'react';
export function TextInput() {
const inputRef = useRef(null);
// inputRef.current — это реальный DOM-элемент
// Изначально null, потом указывает на <input>
const handleClick = () => {
// Прямой доступ к DOM-элементу!
inputRef.current?.focus();
console.log(inputRef.current.value); // Значение инпута
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Сфокусировать</button>
</>
);
}
Когда React рендерит <input ref={inputRef} ... />, он:
- Создаёт реальный DOM-элемент
- Заполняет inputRef.current этим элементом
- После этого ты можешь вызывать методы:
.focus(),.blur(),.click()
2. Прямой доступ к DOM API
export function VideoPlayer() {
const videoRef = useRef(null);
const handlePlay = () => {
// Вызов DOM методов напрямую
videoRef.current?.play();
};
const handlePause = () => {
videoRef.current?.pause();
};
const handleSetTime = (seconds) => {
videoRef.current.currentTime = seconds;
};
return (
<>
<video ref={videoRef}>
<source src="video.mp4" type="video/mp4" />
</video>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
<button onClick={() => handleSetTime(10)}>Перейти на 10сек</button>
</>
);
}
Отличие useRef от useState
useState вызывает перерендер:
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
// При каждом вызове setCount() компонент перерендерится!
return (
<button onClick={() => setCount(count + 1)}>
Счётчик: {count}
</button>
);
}
useRef НЕ вызывает перерендер:
import { useRef } from 'react';
export function StopWatch() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current++; // Изменили значение
// Компонент НЕ перерендерится!
console.log('Клики:', countRef.current);
};
return (
<button onClick={handleClick}>
Кликай (открой консоль)
</button>
);
}
Это полезно для:
- Отслеживания количества рендеров
- Хранения ID таймеров для очистки
- Временных значений, которые не нужны в UI
Практические примеры
1. Фокус на инпут при ошибке
export function LoginForm() {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({
email: emailRef.current.value,
password: passwordRef.current.value
})
});
if (!response.ok) {
setError('Неверный пароль');
// Сфокусируем на пароль для удобства пользователя
passwordRef.current?.focus();
}
} catch (err) {
setError(err.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input ref={emailRef} type="email" placeholder="Email" />
<input ref={passwordRef} type="password" placeholder="Пароль" />
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Вход</button>
</form>
);
}
2. Интеграция с внешней библиотекой (jQuery плагин, D3, Leaflet)
import { useRef, useEffect } from 'react';
import * as d3 from 'd3';
export function Chart({ data }) {
const svgRef = useRef(null);
useEffect(() => {
if (!svgRef.current) return;
// Используем D3 с реальным DOM-элементом
const svg = d3.select(svgRef.current);
svg
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', (d, i) => i * 30)
.attr('cy', (d) => d * 10)
.attr('r', 5)
.attr('fill', 'blue');
}, [data]);
return <svg ref={svgRef} width="800" height="400" />;
}
3. Текстовое поле с автосохранением (без вызова useState при каждом символе)
import { useRef, useEffect } from 'react';
export function AutoSaveTextarea() {
const textRef = useRef(null);
const saveTimeoutRef = useRef(null);
const handleChange = () => {
// Очищаем предыдущий таймер
clearTimeout(saveTimeoutRef.current);
// Устанавливаем новый таймер для сохранения
saveTimeoutRef.current = setTimeout(() => {
const text = textRef.current?.value;
console.log('Автосохранение:', text);
// Отправляем на сервер
}, 1000);
};
useEffect(() => {
return () => {
// Очистка при размонтировании
clearTimeout(saveTimeoutRef.current);
};
}, []);
return (
<textarea
ref={textRef}
onChange={handleChange}
placeholder="Напиши текст (автосохранится через 1 сек)"
/>
);
}
4. Выделение текста в инпуте
export function SelectableInput() {
const inputRef = useRef(null);
const selectAll = () => {
inputRef.current?.select();
};
return (
<>
<input
ref={inputRef}
type="text"
defaultValue="Текст для выделения"
/>
<button onClick={selectAll}>Выделить всё</button>
</>
);
}
5. Хранение предыдущего значения State
import { useRef, useEffect, useState } from 'react';
export function PreviousValue() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(null);
useEffect(() => {
// После рендера обновляем prevCountRef
prevCountRef.current = count;
}, [count]);
return (
<>
<p>Текущее: {count}, Предыдущее: {prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
</>
);
}
Жизненный цикл useRef
import { useRef, useEffect } from 'react';
export function LifecycleExample() {
const ref = useRef(null);
useEffect(() => {
console.log('Mount: ref.current =', ref.current);
// На момент первого useEffect ref.current уже указывает на DOM!
return () => {
console.log('Unmount');
// ref.current всё ещё доступен для очистки
};
}, []);
return <div ref={ref}>Содержимое</div>;
}
// Вывод:
// Mount: ref.current = <div>Содержимое</div>
// (при размонтировании) Unmount
Когда НЕ использовать useRef
// ❌ Плохо: используем useRef для значений, влияющих на UI
const countRef = useRef(0);
return (
<button onClick={() => countRef.current++}>
{countRef.current} <!-- Не обновится! -->
</button>
);
// ✅ Хорошо: используем useState
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
Получение информации из DOM
export function GetElementInfo() {
const elementRef = useRef(null);
const getInfo = () => {
const elem = elementRef.current;
console.log('offsetWidth:', elem?.offsetWidth); // Ширина
console.log('offsetHeight:', elem?.offsetHeight); // Высота
console.log('scrollHeight:', elem?.scrollHeight); // Высота с прокруткой
console.log('getBoundingClientRect:', elem?.getBoundingClientRect());
};
return (
<>
<div ref={elementRef} style={{ width: '200px', height: '100px' }}>
Элемент
</div>
<button onClick={getInfo}>Получить информацию</button>
</>
);
}
Типизация useRef в TypeScript
import { useRef } from 'react';
export function TypedRef() {
// Тип элемента
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
inputRef.current?.focus();
const value = inputRef.current?.value; // Type: string | undefined
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Сфокусировать</button>
</>
);
}
// Для хранения значений (не элементов)
export function TypedValue() {
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const startTimer = () => {
timeoutRef.current = setTimeout(() => {
console.log('Время вышло');
}, 1000);
};
return <button onClick={startTimer}>Запустить таймер</button>;
}
Внутренний механизм: как React работает с refs
// Когда ты пишешь:
const ref = useRef(null);
return <div ref={ref} />;
// React внутренне:
// 1. Создаёт реальный DOM-элемент: const domElement = document.createElement('div');
// 2. Присваивает: ref.current = domElement;
// 3. Монтирует в DOM: parent.appendChild(domElement);
// Теперь ref.current указывает на реальный DOM-элемент в браузере!
Заключение
useRef и DOM:
- useRef.current = реальный DOM-элемент после монтирования
- Позволяет вызывать методы:
.focus(),.play(),.select() - Позволяет читать свойства:
.value,.offsetWidth,.classList - НЕ вызывает перерендер при изменении
- Идеален для интеграции с внешними библиотеками
- Следует использовать только когда нет альтернативы через React (props, state, callback)