useRef — это хук в React, который возвращает изменяемый ref-объект, сохраняющийся между рендерами компонента. Основное свойство объекта — current, которое может хранить любое значение.
Ключевые особенности useRef:
.current не вызывает повторный рендерОсновные применения:
Пример доступа к DOM-элементу:
function TextInputWithFocusButton() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Фокус на поле ввода</button>
</>
);
}Хук useRef — это один из встроенных хуков React, который предоставляет способ создания мутируемого значения, не вызывающего повторный рендер при изменении. Это делает его идеальным для определенных сценариев, где состояние useState было бы избыточным или неподходящим. 🔍
useRef возвращает простой JavaScript-объект с одним свойством current:
// Создание ref с начальным значением null
const myRef = useRef(null);
console.log(myRef); // { current: null }
// Можно установить начальное значение
const countRef = useRef(0);
console.log(countRef); // { current: 0 }
// Изменение значения не вызывает ререндер
countRef.current = countRef.current + 1;В отличие от состояния, изменение current происходит синхронно и не вызывает перерисовку компонента.
Самое распространенное применение useRef — получение прямого доступа к DOM-узлам:
function AutoFocusInput() {
const inputRef = useRef(null);
// После монтирования компонента фокусируемся на инпуте
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}function VideoPlayer({ src }) {
const videoRef = useRef(null);
const handlePlay = () => {
videoRef.current.play();
};
const handlePause = () => {
videoRef.current.pause();
};
return (
<div>
<video ref={videoRef} src={src} />
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
</div>
);
}function MeasureExample() {
const divRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
if (divRef.current) {
setDimensions({
width: divRef.current.offsetWidth,
height: divRef.current.offsetHeight
});
}
}, []);
return (
<>
<div ref={divRef} style={{ width: '100%', height: '100px', border: '1px solid black' }}>
Измеряемый элемент
</div>
<p>Ширина: {dimensions.width}px, Высота: {dimensions.height}px</p>
</>
);
}Отслеживание предыдущих значений состояния, которые недоступны напрямую:
function CounterWithPrevious() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
// Сохраняем текущее значение count после рендера
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<h1>Сейчас: {count}, до этого: {prevCount !== undefined ? prevCount : 'Нет предыдущего значения'}</h1>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
</div>
);
}Когда нужно хранить значение, которое не влияет на UI:
function IntervalExample() {
const [count, setCount] = useState(0);
const intervalIdRef = useRef(null);
const startCounter = () => {
if (intervalIdRef.current !== null) return;
intervalIdRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopCounter = () => {
clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
};
// Очистка при размонтировании
useEffect(() => {
return () => {
if (intervalIdRef.current !== null) {
clearInterval(intervalIdRef.current);
}
};
}, []);
return (
<div>
<h1>Счетчик: {count}</h1>
<button onClick={startCounter}>Старт</button>
<button onClick={stopCounter}>Стоп</button>
</div>
);
}Сохранение результатов вычислений, которые не должны приводить к перерисовке:
function ExpensiveComponent({ data }) {
const cachedDataRef = useRef(null);
if (cachedDataRef.current === null || cachedDataRef.current.originalData !== data) {
// Выполнить дорогостоящие вычисления только при изменении data
const processedResult = expensiveCalculation(data);
cachedDataRef.current = {
originalData: data,
processedResult
};
}
// Используем кэшированный результат
return <div>{cachedDataRef.current.processedResult}</div>;
}Пример хука для отслеживания, был ли компонент смонтирован:
function useIsMounted() {
const isMountedRef = useRef(false);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
}
// Использование
function AsyncComponent() {
const [data, setData] = useState(null);
const isMountedRef = useIsMounted();
useEffect(() => {
fetchData().then(result => {
// Проверяем, смонтирован ли компонент перед обновлением состояния
if (isMountedRef.current) {
setData(result);
}
});
}, []);
return <div>{data ? JSON.stringify(data) : 'Загрузка...'}</div>;
}// С useState - перерисовка при каждом изменении
function CounterWithState() {
const [count, setCount] = useState(0);
// Перерисовывает компонент
const increment = () => setCount(count + 1);
console.log("Рендер компонента с useState");
return (
<div>
<p>Счетчик (useState): {count}</p>
<button onClick={increment}>Увеличить</button>
</div>
);
}
// С useRef - без перерисовок
function CounterWithRef() {
const countRef = useRef(0);
const [, forceUpdate] = useState({});
// Не вызывает перерисовку
const increment = () => {
countRef.current += 1;
};
// Для отображения актуального значения нужно вызвать ререндер
const incrementAndUpdate = () => {
increment();
forceUpdate({});
};
console.log("Рендер компонента с useRef");
return (
<div>
<p>Счетчик (useRef): {countRef.current}</p>
<button onClick={increment}>Увеличить (без обновления UI)</button>
<button onClick={incrementAndUpdate}>Увеличить и обновить UI</button>
</div>
);
}function CompareRefs() {
// Создается заново при каждом рендере
const createRefExample = React.createRef();
// Сохраняется между рендерами
const useRefExample = useRef();
// Демонстрация различий
const [, forceRender] = useState({});
useEffect(() => {
console.log("После монтирования:");
console.log("createRef текущее значение:", createRefExample.current);
console.log("useRef текущее значение:", useRefExample.current);
// Устанавливаем значения
createRefExample.current = "Значение createRef";
useRefExample.current = "Значение useRef";
console.log("После установки:");
console.log("createRef значение:", createRefExample.current);
console.log("useRef значение:", useRefExample.current);
}, []);
const handleClick = () => {
// Перерисовываем компонент
forceRender({});
// После перерисовки
console.log("После перерисовки:");
console.log("createRef значение:", createRefExample.current); // null
console.log("useRef значение:", useRefExample.current); // "Значение useRef"
};
return <button onClick={handleClick}>Перерисовать</button>;
}// ❌ Неправильно: useEffect не отслеживает изменения ref.current
function WrongWayToWatchRef() {
const countRef = useRef(0);
useEffect(() => {
console.log("Значение изменилось:", countRef.current);
}, [countRef.current]); // Это не сработает как ожидается
return (
<button onClick={() => { countRef.current += 1; }}>
Увеличить (не вызовет useEffect)
</button>
);
}
// ✅ Правильно: использовать state для отслеживаемых значений
function CorrectWayToWatchChanges() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Значение изменилось:", count);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Увеличить (вызовет useEffect)
</button>
);
}// ❌ Неправильно: создание ref внутри условия
function ConditionalRef({ shouldRender }) {
let inputRef;
if (shouldRender) {
inputRef = useRef(null);
}
// При изменении shouldRender возникнет ошибка
return shouldRender ? <input ref={inputRef} /> : null;
}
// ✅ Правильно: всегда создавать ref на верхнем уровне
function CorrectConditionalRendering({ shouldRender }) {
const inputRef = useRef(null);
return shouldRender ? <input ref={inputRef} /> : null;
}// ❌ Неправильно: обращение к ref до его присвоения
function TooEarlyAccess() {
const inputRef = useRef(null);
// Этот код выполнится до того, как ref получит значение
console.log(inputRef.current.value); // Ошибка: Cannot read property 'value' of null
return <input ref={inputRef} defaultValue="Начальное значение" />;
}
// ✅ Правильно: использовать useEffect для доступа после рендера
function CorrectAccess() {
const inputRef = useRef(null);
useEffect(() => {
// Этот код выполнится после того, как DOM будет готов
console.log(inputRef.current.value);
}, []);
return <input ref={inputRef} defaultValue="Начальное значение" />;
}✅ useRef в React используется для:
✅ Ключевые особенности:
.current не вызывает повторного рендераuseState отсутствием автоматической перерисовкиcreateRef сохранением значения между рендерамиПонимание useRef и правильное его применение — важная часть оптимизации производительности и взаимодействия с DOM в React-приложениях. 🚀
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪