Глубокое Погружение в Работу с Таймерами в React
- вторник, 11 марта 2025 г. в 00:00:05
Работа с таймерами в React требует понимания нескольких ключевых концептов. Давайте разберем, как создать надежные таймеры с нуля, и познакомимся с готовыми решениями.
При реализации таймеров в React важно учитывать:
Очистку ресурсов при размонтировании компонента
Корректную работу с замыканиями
Обработку изменений состояния компонента
При работе с таймерами в React, комбинация useEffect и useRef решает несколько критических проблем, которые могут возникнуть при наивной реализации. Давайте разберем, почему эти хуки являются ключевыми компонентами надежного таймера.
useEffect выступает как "менеджер жизненного цикла" таймера. Когда компонент монтируется или изменяется состояние active, useEffect создает новый интервал. Функция очистки (cleanup) гарантирует, что старый интервал будет удален перед созданием нового или при размонтировании компонента. Без cleanup-функции в useEffect, каждое изменение зависимостей создавало бы новый интервал, не очищая старый, что приводило бы к множественным одновременно работающим таймерам.
useRef решает несколько критических проблем:
В отличие от обычных переменных, которые пересоздаются при каждом рендере, useRef сохраняет значение между рендерами. Это особенно важно для:
Хранения ID интервала для последующей очистки
Сохранения актуальной версии callback-функции
Без useRef, функция в setInterval замыкается на той версии callback, которая была актуальна при создании эффекта. С useRef мы всегда имеем доступ к актуальной версии callback.
Начнем с простого интервала. Вот полная реализация хука useInterval:
import { useEffect, useRef, useState } from 'react';
export const useInterval = (callback: () => void, interval = 1000) => {
const [active, setActive] = useState(true);
const intervalIdRef = useRef<ReturnType<typeof setInterval>>();
const callbackRef = useRef(callback);
// Обновляем референс при изменении callback
callbackRef.current = callback;
useEffect(() => {
if (!active) return;
// Используем callbackRef для доступа к актуальной версии callback
intervalIdRef.current = setInterval(() => callbackRef.current(), interval);
// Очистка при размонтировании или изменении зависимостей
return () => {
clearInterval(intervalIdRef.current);
};
}, [active, interval]);
return {
active,
pause: () => setActive(false),
resume: () => setActive(true),
toggle: () => setActive(prev => !prev)
};
};
useRef для callback:
const callbackRef = useRef(callback);
callbackRef.current = callback;
Используем useRef для хранения актуальной версии callback-функции, избегая проблем с замыканиями.
Управление состоянием:
const [active, setActive] = useState(true);
Состояние active контролирует работу интервала.
Эффект и очистка
useEffect(() => {
if (!active) return;
intervalIdRef.current = setInterval(() => callbackRef.current(), interval);
return () => clearInterval(intervalIdRef.current);
}, [active, interval]);
Хотя понимание механизма работы таймеров важно, в реальных проектах часто удобнее использовать готовые, протестированные решения. Библиотека @sibericancode/reactuse предоставляет все эти функции и даже больше:
Полная типизация TypeScript
Дополнительные опции и callback'и
Оптимизированная производительность
Тестовое покрытие
Регулярные обновления и поддержка
import { useTimer } from '@sibericancode/reactuse';
const { active, days, hours, minutes, seconds } = useTimer(86400, {
immediately: true,
onExpire: () => console.log('Готово!'),
onTick: (s) => console.log(`Осталось ${s} секунд`)
});
Вместо того чтобы реализовывать эти механизмы самостоятельно, вы можете сосредоточиться на бизнес-логике вашего приложения, используя надежные и проверенные инструменты из https://siberiacancode.github.io/reactuse/