Таймеры и временные операции

Hyperapp — это лёгкий фронтенд-фреймворк для построения реактивных пользовательских интерфейсов на JavaScript. Несмотря на минимализм, он обеспечивает мощные средства для управления состоянием и реакциями на события, включая работу с таймерами и временными операциями. Таймеры позволяют создавать отложенные действия, периодические обновления данных и анимации, интегрированные с состоянием приложения.

В Hyperapp таймеры чаще всего используются через эффекты (effects), которые позволяют отделить побочные действия от чистого состояния. Эффект — это функция, принимающая аргументы и возвращающая функцию, которая выполняется после обновления состояния.

const delayEffect = (ms, action) => (dispatch) => {
  setTimeout(() => dispatch(action), ms);
};

Здесь delayEffect принимает задержку в миллисекундах и действие (action), которое нужно выполнить через заданное время. Функция возвращает другую функцию, которая получает dispatch и инициирует таймер. Такой подход полностью соответствует концепции Hyperapp: побочные эффекты управляются централизованно и не смешиваются с чистым состоянием.


Периодические операции и setInterval

Для создания повторяющихся операций используется стандартный setInterval. В Hyperapp его удобно интегрировать через эффекты:

const intervalEffect = (ms, action) => (dispatch) => {
  const id = setInterval(() => dispatch(action), ms);
  return () => clearInterval(id);
};
  • Возврат функции очистки: Hyperapp поддерживает автоматическую отмену эффектов при удалении компонента, если эффект возвращает функцию. В приведённом примере эта функция вызывается для остановки интервала, предотвращая утечки памяти.
  • Передача состояния в действие: действие может использовать текущее состояние приложения для вычисления новых значений.

Пример использования с простым счётчиком:

const state = { count: 0 };

const actions = {
  tick: () => (state) => ({ count: state.count + 1 }),
  startTimer: () => (state, actions) => intervalEffect(1000, actions.tick)
};

Каждую секунду состояние увеличивается на единицу, и компонент автоматически ререндерится.


Таймеры с отменой

Иногда нужно иметь возможность остановить таймер вручную. Для этого состояние может хранить идентификатор таймера:

const state = { count: 0, timerId: null };

const actions = {
  start: () => (state, actions) => ({
    timerId: setInterval(() => actions.tick(), 1000)
  }),
  stop: (state) => {
    clearInterval(state.timerId);
    return { timerId: null };
  },
  tick: (state) => ({ count: state.count + 1 })
};
  • Отделение логики запуска и остановки: запуск и остановка таймера оформлены отдельными действиями, что делает код более читаемым и предсказуемым.
  • Безопасность: перед повторным запуском можно проверять, активен ли таймер, чтобы избежать дублирования.

Использование requestAnimationFrame

Для плавной анимации лучше использовать requestAnimationFrame, так как он синхронизирован с обновлением экрана:

const animationEffect = (update) => (dispatch) => {
  let running = true;
  
  const loop = () => {
    if (!running) return;
    dispatch(update);
    requestAnimationFrame(loop);
  };

  requestAnimationFrame(loop);

  return () => { running = false; };
};
  • Цикл анимации: requestAnimationFrame вызывает функцию перед следующим ререндером браузера, обеспечивая высокую производительность.
  • Контроль состояния: переменная running позволяет останавливать анимацию в любой момент, интегрируя её с жизненным циклом компонента.

Пример использования с движущимся объектом:

const state = { x: 0 };

const actions = {
  move: (state) => ({ x: state.x + 2 }),
  startMove: () => (state, actions) => animationEffect(actions.move)
};

Объект будет плавно двигаться по горизонтали, обновляясь синхронно с кадрами браузера.


Сложные временные сценарии

Hyperapp позволяет комбинировать таймеры и анимации для более сложных сценариев:

  • Пошаговые анимации: последовательное выполнение действий с разной задержкой.
  • Повторяющиеся задачи с динамическим интервалом: интервал может изменяться в зависимости от состояния.
  • Таймеры с условиями остановки: выполнение продолжается только пока состояние удовлетворяет заданному условию.

Пример пошаговой анимации:

const state = { step: 0 };

const actions = {
  nextStep: (state) => ({ step: state.step + 1 }),
  startSteps: () => (state, actions) => {
    const steps = [500, 1000, 1500]; 
    steps.forEach((ms, index) => {
      setTimeout(() => actions.nextStep(), ms);
    });
  }
};
  • Разделение задержек: каждая следующая операция запускается с индивидуальной задержкой.
  • Состояние шага: состояние обновляется по мере выполнения каждого шага, сохраняя историю.

Интеграция с жизненным циклом компонентов

Эффекты таймеров и анимаций должны корректно взаимодействовать с жизненным циклом компонентов:

  • Монтирование: таймеры запускаются после создания компонента через эффект.
  • Размонтирование: возвращаемая функция эффекта должна останавливать таймер, чтобы избежать утечек памяти.
  • Обновление: при смене параметров таймера старый эффект можно отменять и запускать новый с обновлёнными значениями.

Пример интеграции с компонентом:

const TimerComponent = ({ interval }) => (state, actions) => ({
  oncreate: () => intervalEffect(interval, actions.tick)
});

Таймер будет автоматически запускаться при создании компонента и прекращаться при его удалении.


Практические рекомендации

  • Использовать эффекты для всех таймеров: это упрощает управление и делает код более предсказуемым.
  • Возвращать функцию очистки: предотвращает утечки памяти и ошибки при размонтировании компонентов.
  • Избегать глобальных таймеров: интеграция с состоянием Hyperapp делает управление временем безопасным и реактивным.
  • Комбинировать с анимациями: requestAnimationFrame обеспечивает плавность обновлений и минимальную нагрузку на процессор.

Hyperapp предоставляет все необходимые механизмы для точного контроля времени в приложении, объединяя чистое состояние и побочные эффекты в единую архитектуру, что позволяет создавать как простые таймеры, так и сложные анимации и последовательные операции.