Антон Ларичев

Введение
useState и useEffect — два самых важных хука в React. Они появились в версии 16.8 и полностью изменили подход к написанию компонентов: теперь не нужны классы, чтобы работать с состоянием и жизненным циклом. В этой статье разберём оба хука детально — от базового синтаксиса до типичных ошибок.
Хук useState
useState позволяет добавить локальное состояние в функциональный компонент. Хук возвращает массив из двух элементов: текущее значение состояния и функцию для его обновления.
import { useState } from 'react';
function Counter() {
// начальное значение счётчика — 0
const [count, setCount] = useState(0);
return (
<div>
<p>Счётчик: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
<button onClick={() => setCount(count - 1)}>Уменьшить</button>
</div>
);
}
Обновление на основе предыдущего значения
Если новое состояние зависит от предыдущего, передавай функцию-обновитель вместо значения. Это гарантирует корректную работу при асинхронных обновлениях:
// Так делать не стоит — count может быть устаревшим
setCount(count + 1);
// Правильный способ — используем актуальное предыдущее значение
setCount(prev => prev + 1);
Состояние с объектом
useState работает с любым типом данных. При работе с объектами важно помнить: React сравнивает состояния по ссылке, поэтому нужно создавать новый объект при каждом обновлении:
function UserForm() {
const [user, setUser] = useState({ name: '', email: '' });
const handleNameChange = (e) => {
// создаём новый объект, копируем старые поля и перезаписываем нужное
setUser(prev => ({ ...prev, name: e.target.value }));
};
return (
<input
value={user.name}
onChange={handleNameChange}
placeholder="Имя"
/>
);
}
Ленивая инициализация
Если начальное состояние вычисляется дорого (например, парсинг данных из localStorage), передай функцию — она выполнится только при первом рендере:
// Эта функция вызовется только один раз при монтировании
const [data, setData] = useState(() => {
const saved = localStorage.getItem('userData');
return saved ? JSON.parse(saved) : { name: '', theme: 'light' };
});
Хук useEffect
useEffect используется для выполнения побочных эффектов: запросов к API, подписок на события, работы с DOM, таймеров. Он принимает функцию-эффект и массив зависимостей.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// этот код запустится после каждого рендера, где изменился userId
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]); // зависимость — userId
if (loading) return <p>Загрузка...</p>;
return <h2>{user?.name}</h2>;
}
Массив зависимостей
Поведение useEffect полностью определяется вторым аргументом:
// Запускается после каждого рендера
useEffect(() => {
console.log('рендер');
});
// Запускается только один раз при монтировании
useEffect(() => {
console.log('компонент смонтирован');
}, []);
// Запускается при монтировании и при изменении value
useEffect(() => {
console.log('value изменился:', value);
}, [value]);
Функция очистки
Если эффект создаёт подписки, таймеры или слушатели событий — их нужно очищать. Для этого возвращай функцию из эффекта:
useEffect(() => {
// подписываемся на событие resize
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// эта функция вызовется перед размонтированием или перед следующим эффектом
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
Отмена запроса при размонтировании
При работе с fetch важно обрабатывать ситуацию, когда компонент размонтируется до завершения запроса:
useEffect(() => {
// флаг для отслеживания актуальности компонента
let isMounted = true;
fetch(`/api/posts/${postId}`)
.then(res => res.json())
.then(data => {
// обновляем состояние только если компонент ещё жив
if (isMounted) {
setPosts(data);
}
});
return () => {
isMounted = false;
};
}, [postId]);
Совместное использование useState и useEffect
Пример реального компонента поиска с дебаунсом:
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
useEffect(() => {
if (!query.trim()) {
setResults([]);
return;
}
setIsSearching(true);
// откладываем запрос на 500мс после последнего ввода
const timer = setTimeout(async () => {
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
setResults(data.items);
setIsSearching(false);
}, 500);
// отменяем таймер если query изменился раньше
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Поиск..."
/>
{isSearching && <p>Ищем...</p>}
<ul>
{results.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
Частые ошибки
1. Бесконечный цикл в useEffect
Если внутри эффекта обновлять состояние, которое указано в зависимостях — получится бесконечный цикл:
// Плохо: data в зависимостях, а внутри мы её обновляем
useEffect(() => {
setData([...data, newItem]); // вызовет перерендер → эффект запустится снова
}, [data]);
// Хорошо: используем функцию-обновитель, убираем data из зависимостей
useEffect(() => {
setData(prev => [...prev, newItem]);
}, [newItem]);
2. Пропуск зависимостей
Никогда не игнорируй предупреждения ESLint о зависимостях useEffect. Пропуск переменной в массиве зависимостей приводит к работе с устаревшими данными (stale closure).
3. Прямая мутация состояния
// Плохо: мутируем массив напрямую — React не увидит изменение
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // React не вызовет перерендер
setItems(items);
// Хорошо: создаём новый массив
setItems(prev => [...prev, 4]);
4. Асинхронная функция в useEffect
useEffect не может принимать async-функцию напрямую. Объяви её внутри:
// Плохо
useEffect(async () => { ... }, []);
// Хорошо
useEffect(() => {
const fetchData = async () => {
const data = await getData();
setState(data);
};
fetchData();
}, []);
Заключение
useState и useEffect — фундамент работы с React-компонентами. useState управляет локальными данными компонента и перерисовкой, useEffect синхронизирует компонент с внешним миром. Ключевые правила: всегда указывай зависимости честно, не мутируй состояние напрямую и не забывай про функцию очистки. Освоив эти два хука на практике, ты будешь готов к изучению более сложных — useReducer, useContext и useCallback.






Комментарии
0