Эффекты и useRef
Когда нужен useEffect, cleanup и когда он НЕ нужен.
Обновлено
Что такое эффект
Компонент должен быть чистым — он только вычисляет JSX. Но иногда нужно синхронизировать компонент с чем-то вне React: таймером, WebSocket-соединением, API браузера, не-React-библиотекой. Для этого есть useEffect: код, который выполняется после того, как React обновил экран.
useEffect(() => {
const id = setInterval(tick, 1000) // запускаете синхронизацию
return () => clearInterval(id) // cleanup: останавливаете её
}, []) // зависимостиЖизненный цикл эффекта
- Компонент рендерится и появляется на экране.
- Эффект выполняется.
- Когда меняются зависимости: React выполняет старый cleanup, затем эффект заново.
- При размонтировании выполняется cleanup.
Зависимости
| Второй аргумент | Эффект выполняется |
|---|---|
| нет | после каждого рендера |
[] |
один раз, после монтирования |
[a, b] |
после монтирования и когда меняются a или b (сравнение через Object.is) |
В список кладут всё, что эффект использует из props и состояния. Помогает правило ESLint react-hooks/exhaustive-deps.
Возможно, эффект вам не нужен
Самая частая ошибка в React — эффекты для того, что не является синхронизацией с внешним миром.
| Вместо эффекта, чтобы... | Делайте так |
|---|---|
| вычислить данные из состояния / props | вычисляйте прямо при рендере |
| отреагировать на клик / отправку | логику — в обработчик события |
| сбросить состояние при смене prop | key={id} на компоненте |
| получить данные с сервера | Server Component или TanStack Query |
| сообщить родителю, что что-то изменилось | вызовите колбэк в обработчике, который сделал изменение |
Ключевой вопрос: «Этот код выполняется, потому что компонент появился на экране или потому что пользователь что-то сделал?» Первое → эффект. Второе → обработчик события.
Fetch в эффекте — если без этого никак
useEffect(() => {
const controller = new AbortController()
fetch(`/api/users/${id}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(() => {})
return () => controller.abort() // отменяет старый запрос, если id быстро меняется
}, [id])Без cleanup получите состояние гонки: старый ответ может прийти после нового и перезаписать его.
useRef — память без перерисовки
useState |
useRef |
|
|---|---|---|
| Изменение вызывает рендер | да | нет |
| Чтение во время рендера | да | нет (только в эффектах / обработчиках) |
| Для | того, что видно на экране | ссылок на DOM, id таймеров, значений «за кулисами» |
const inputRef = useRef<HTMLInputElement>(null)
<input ref={inputRef} />
inputRef.current?.focus()Strict Mode
В режиме разработки React монтирует → размонтирует → снова монтирует каждый компонент, чтобы проверить, что у эффектов правильный cleanup. Если что-то «выполняется дважды» и ломается, у эффекта нет cleanup. В продакшене он выполняется один раз.
Коротко
- Эффект = синхронизация с чем-то вне React; cleanup обязателен, если вы что-то запускаете.
- Зависимости = всё, что использует эффект.
- Вычисления → при рендере; реакции на действия → обработчики; данные → Server Components / TanStack Query.