javascript

Глубокое Погружение в Работу с Таймерами в React

  • вторник, 11 марта 2025 г. в 00:00:05
https://habr.com/ru/articles/889690/

Работа с таймерами в React требует понимания нескольких ключевых концептов. Давайте разберем, как создать надежные таймеры с нуля, и познакомимся с готовыми решениями.

Базовые Принципы Работы с Таймерами

При реализации таймеров в React важно учитывать:

  1. Очистку ресурсов при размонтировании компонента

  2. Корректную работу с замыканиями

  3. Обработку изменений состояния компонента

Почему useEffect и useRef Так Важны для Таймеров?

При работе с таймерами в React, комбинация useEffect и useRef решает несколько критических проблем, которые могут возникнуть при наивной реализации. Давайте разберем, почему эти хуки являются ключевыми компонентами надежного таймера.

Роль useEffect

useEffect выступает как "менеджер жизненного цикла" таймера. Когда компонент монтируется или изменяется состояние active, useEffect создает новый интервал. Функция очистки (cleanup) гарантирует, что старый интервал будет удален перед созданием нового или при размонтировании компонента. Без cleanup-функции в useEffect, каждое изменение зависимостей создавало бы новый интервал, не очищая старый, что приводило бы к множественным одновременно работающим таймерам.

Магия useRef

useRef решает несколько критических проблем:

В отличие от обычных переменных, которые пересоздаются при каждом рендере, useRef сохраняет значение между рендерами. Это особенно важно для:

  1. Хранения ID интервала для последующей очистки

  2. Сохранения актуальной версии 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)
  };
};

Разбор Механизма:

  1. useRef для callback:

    const callbackRef = useRef(callback);
    callbackRef.current = callback;

    Используем useRef для хранения актуальной версии callback-функции, избегая проблем с замыканиями.

  2. Управление состоянием:

    const [active, setActive] = useState(true);
    

    Состояние active контролирует работу интервала.

  3. Эффект и очистка

    useEffect(() => {
      if (!active) return;
      intervalIdRef.current = setInterval(() => callbackRef.current(), interval);
      return () => clearInterval(intervalIdRef.current);
    }, [active, interval]);

Готовое Решение: @sibericancode/reactuse

Хотя понимание механизма работы таймеров важно, в реальных проектах часто удобнее использовать готовые, протестированные решения. Библиотека @sibericancode/reactuse предоставляет все эти функции и даже больше:

  1. Полная типизация TypeScript

  2. Дополнительные опции и callback'и

  3. Оптимизированная производительность

  4. Тестовое покрытие

  5. Регулярные обновления и поддержка

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/