Один из самых частых когнитивных диссонансов у начинающих React-разработчиков — поведение локальных переменных при rerender. Вопрос на Reddit как раз об этом: почему в одном случае переменная сбрасывается, а в другом (с тем же setState) — нет. Давайте разберёмся без хайпа, как это работает на практике.
Локальные переменные vs React State
Когда компонент ререндерится, React выполняет функцию компонента заново. Это значит:
function Counter() {
let count = 0; // Инициализируется при каждом render
const handleClick = () => {
count = count + 1;
console.log(count); // В консоли будет +1, но UI не обновится
};
return <button onClick={handleClick}>{count}</button>;
}
Здесь count — обычная переменная. При клике она увеличивается, но:
- React не знает об изменении
- При следующем render переменная снова инициализируется как
0
Почему так? Потому что это просто переменная в scope функции, а не часть реактивного state. React не отслеживает такие изменения.
Управляемые инпуты и сохранение состояния
Теперь смотрим на пример из официальной документации:
function Clock() {
const [time, setTime] = useState(new Date());
// Эффект для обновления времени
useEffect(() => {
const timerID = setInterval(() => {
setTime(new Date()); // Триггерим rerender
}, 1000);
return () => clearInterval(timerID);
}, []);
return (
<div>
<input />
<p>{time.toLocaleTimeString()}</p>
</div>
);
}
Почему значение в <input> не сбрасывается, хотя компонент ререндерится каждую секунду? Всё дело в том, как работает reconciliation:
- React сохраняет состояние инпута между renders, если это управляемый компонент (без явного указания
value) или uncontrolled компонент сdefaultValue - При rerender React сравнивает виртуальный DOM и обновляет только то, что изменилось (в данном случае — только
timeв параграфе)
Где ловушки?
-
Кастомные компоненты без ключей:
{items.map(item => <ChildComponent data={item} />)}При rerender React может пересоздать компоненты, если забыли про
key -
Inline-функции в пропсах:
<Button onClick={() => doSomething()} />Создаётся новая функция при каждом render, что может привести к неожиданному ререндеру дочерних компонентов
Что делать на практике?
- Для данных, которые должны сохраняться между renders — только
useState,useRefили внешние стейт-менеджеры - Для вычислений, которые не должны запускаться при каждом render —
useMemo - Для колбэков, которые передаются вниз по дереву —
useCallback
Важный нюанс: useRef сохраняет значение между renders, но не триггерит обновление. Пример счётчика с правильной реализацией:
function Counter() {
const countRef = useRef(0);
const [_, forceUpdate] = useState(); // Для триггера rerender
const handleClick = () => {
countRef.current += 1;
forceUpdate({}); // Явно запрашиваем rerender
};
return <button onClick={handleClick}>{countRef.current}</button>;
}
Какой вывод? React не магия — это модель, где явное лучше неявного. Если нужно сохранять данные между renders, всегда используйте хуки состояния или рефы. Локальные переменные в функции компонента — это всего лишь локальные переменные, которые React даже не пытается отслеживать.
Источник: https://www.reddit.com/r/reactjs/comments/1upd3mk/need_help_understanding_state/