В чем разница между useRef и useState при хранении значения?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между useRef и useState при хранении значения
useRef и useState оба хранят значения, но имеют фундаментально разные поведения. Выбор между ними влияет на перерисовку компонента, способность отследить изменения и производительность.
useState — для состояния которое должно влиять на UI
useState обновляет UI при изменении значения:
function Counter() {
const [count, setCount] = useState(0);
console.log('Component rendered');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// При клике:
// 1. setCount вызывается
// 2. React увидит новое значение count
// 3. Компонент перерисуется
// 4. Console логирует "Component rendered"
useRef — для значения которое НЕ должно влиять на UI
useRef НЕ обновляет UI при изменении значения:
function CounterRef() {
const countRef = useRef(0);
const [dummy, setDummy] = useState(0); // для перерисовки
console.log('Component rendered');
return (
<div>
<p>Count: {countRef.current}</p>
<button onClick={() => {
countRef.current += 1;
// UI не обновится автоматически!
console.log('countRef.current:', countRef.current); // логирует
}}>+1 (без перерисовки)</button>
<button onClick={() => setDummy(dummy + 1)}>
Перерисовать компонент
</button>
</div>
);
}
// При клике на первую кнопку:
// 1. countRef.current изменяется
// 2. Компонент НЕ перерисуется
// 3. Console НЕ логирует "Component rendered"
// 4. Но значение в консоли правильное!
Таблица сравнения
| Аспект | useState | useRef |
|---|---|---|
| Перерисовка | ✅ Вызывает перерисовку | ❌ НЕ вызывает |
| Присвоение | setState(value) | ref.current = value |
| Доступ | value | value.current |
| Мутация | ❌ Запрещена | ✅ Разрешена |
| Начальное значение | Функция или значение | Функция или значение |
| Синхронность | ❌ Асинхронно | ✅ Синхронно |
| Сохранение между рендерами | ✅ Да | ✅ Да |
| Обновление DOM | ✅ При изменении | ❌ Нужно вручную |
Пример 1: Счётчик кликов
С useState (правильный способ для UI):
function ClickCounter() {
const [clicks, setClicks] = useState(0);
return (
<div>
<p>Кликов: {clicks}</p> {/* Обновляется автоматически */}
<button onClick={() => setClicks(clicks + 1)}>Click</button>
</div>
);
}
С useRef (неправильный способ для UI):
function ClickCounterRef() {
const clicksRef = useRef(0);
const [, forceUpdate] = useState(0);
const handleClick = () => {
clicksRef.current += 1;
forceUpdate(clicksRef.current); // Нужно вручную перерисовать!
};
return (
<div>
<p>Кликов: {clicksRef.current}</p>
<button onClick={handleClick}>Click</button>
</div>
);
}
❌ Не делай так! Используй useState для UI.
Пример 2: Доступ к DOM элементу
Правильное использование useRef:
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // Прямой доступ к DOM
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</div>
);
}
// useRef не вызывает перерисовку
// Это просто ссылка на DOM элемент
Пример 3: Хранение предыдущего значения
Отслеживание изменений:
function PreviousValue({ name }) {
const prevNameRef = useRef();
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
// Сохранить предыдущее значение ДО следующего рендера
prevNameRef.current = name;
}, [name]);
return (
<div>
<p>Текущее: {name}</p>
<p>Предыдущее: {prevNameRef.current}</p>
<p>Рендеров: {renderCount}</p>
<button onClick={() => setRenderCount(renderCount + 1)}>
Перерисовать
</button>
</div>
);
}
// useRef НЕ вызывает перерисовку
// Идеален для отслеживания старых значений
Пример 4: Таймер
Нужно сохранить ID таймера:
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null); // ✅ useRef для ID
const start = () => {
if (intervalRef.current) return; // Уже запущен
intervalRef.current = setInterval(() => {
setSeconds(prev => prev + 1); // Обновить UI
}, 1000);
};
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
return (
<div>
<p>Времени: {seconds}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
// useRef сохраняет ID между рендерами
// Без перерисовки компонента
Пример 5: Счётчик БЕЗ обновления UI
Иногда нужно считать, но не обновлять UI:
function HiddenCounter() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
// Считается в фоне, UI не обновляется
console.log('Действительных кликов:', countRef.current);
};
const logCount = () => {
alert(`Всего кликов: ${countRef.current}`);
};
return (
<div>
<button onClick={handleClick}>Click (не видно счёта)</button>
<button onClick={logCount}>Show Count</button>
</div>
);
}
// Счётчик работает, но UI не переживает излишних перерисовок
Когда использовать useState
✅ Используй useState когда:
- Значение должно отражаться в UI
- Нужна перерисовка компонента
- Данные влияют на вывод компонента
- Типичное состояние приложения (form values, filters, etc.)
Когда использовать useRef
✅ Используй useRef когда:
- Нужна прямая ссылка на DOM элемент
- Нужно сохранить значение БЕЗ перерисовки
- Хранишь ID таймера/интервала
- Отслеживаешь предыдущее значение
- Кешируешь данные которые не меняют UI
Частые ошибки
❌ Ошибка 1: useRef вместо useState
// Неправильно!
function Form() {
const nameRef = useRef('');
return (
<div>
<input
ref={nameRef}
onChange={(e) => nameRef.current = e.target.value}
/>
<p>{nameRef.current}</p> {/* НЕ обновится! */}
</div>
);
}
// Правильно!
function Form() {
const [name, setName] = useState('');
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>{name}</p> {/* Обновится! */}
</div>
);
}
❌ Ошибка 2: useState для DOM ссылок
// Неправильно!
function Video() {
const [videoRef, setVideoRef] = useState(null);
return <video ref={videoRef} />; // Вызывает лишние перерисовки
}
// Правильно!
function Video() {
const videoRef = useRef(null);
return <video ref={videoRef} />; // Никаких перерисовок
}
Золотое правило: если значение должно быть видно в UI — используй useState. Если это вспомогательное значение для логики или DOM ссылка — используй useRef.