Подписки и наблюдатели

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

Ключевая идея заключается в том, что источник данных (анимация, жест, таймер, физическая симуляция) не хранится как статическое значение. Он представляет собой поток, который можно наблюдать через механизм подписки.

Базовая модель наблюдателя

Любой поток в Popmotion поддерживает подписку через метод subscribe. Подписка принимает объект-наблюдатель, который описывает реакцию на различные события жизненного цикла потока:

  • поступление нового значения
  • завершение потока
  • возникновение ошибки

Структура наблюдателя выглядит следующим образом:

const observer = {
  next: (v) => {},
  complete: () => {},
  error: (e) => {}
};

Минимальная форма допускает только обработчик значений:

const observer = (v) => {
  // обработка каждого нового значения
};

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

Подписка и управление жизненным циклом

Метод subscribe возвращает функцию отписки. Это ключевой механизм управления ресурсами, особенно в долгоживущих интерфейсах и сложных анимационных сценах.

const subscription = motion.subscribe({
  next: v => console.log(v),
  complete: () => console.log('done')
});

// прекращение получения значений
subscription();

Отписка разрывает связь между источником и наблюдателем, предотвращая дальнейшие вызовы и освобождая ресурсы.

В Popmotion поток может завершиться автоматически, например, при окончании tween-анимации или остановке физической симуляции. В этом случае вызывается complete.

Потоки как абстракция времени

Подписочная модель Popmotion не является просто альтернативой событиям DOM. Она представляет временную ось как последовательность дискретных значений.

Каждый вызов next соответствует конкретному моменту времени внутри анимации:

motion.subscribe(v => {
  // v — состояние на текущем кадре
});

В анимационных потоках значение обычно содержит:

  • прогресс (0–1)
  • вычисленные координаты
  • физические параметры (скорость, ускорение)
  • производные значения (например, сглаженные траектории)

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

Многоподписочная модель

Один и тот же поток может иметь несколько независимых подписчиков. Каждый из них получает одинаковую последовательность значений.

const stream = motion;

stream.subscribe(v => {
  console.log('логирование:', v);
});

stream.subscribe(v => {
  render(v);
});

Подписчики не влияют друг на друга. Поток не дублирует вычисления для каждого подписчика — вычисления происходят один раз, затем результаты распространяются всем наблюдателям.

События завершения и ошибки

Помимо передачи значений, поток поддерживает два дополнительных сигнала управления:

Завершение потока

complete вызывается, когда поток естественным образом заканчивается. Это характерно для:

  • tween-анимаций с фиксированной длительностью
  • одноразовых действий
  • последовательных цепочек
motion.subscribe({
  next: v => {},
  complete: () => {
    console.log('анимация завершена');
  }
});

Завершение является однократным событием и всегда происходит после последнего next.

Ошибка потока

error сигнализирует о некорректном состоянии выполнения:

motion.subscribe({
  error: (err) => {
    console.log('ошибка потока', err);
  }
});

После вызова error поток прекращает дальнейшую эмиссию значений и считается завершённым.

Отписка как механизм контроля ресурсов

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

Особенно это важно в сценариях:

  • частые анимации (scroll, drag)
  • интерактивные жесты
  • динамическое создание/удаление компонентов
  • параллельные физические симуляции

Типичный паттерн использования:

const stop = motion.subscribe(update);

setTimeout(() => {
  stop();
}, 2000);

Такой подход предотвращает накопление неиспользуемых подписок.

Подписка внутри композиции потоков

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

const spring = createSpring();

const enhanced = spring.subscribe(v => {
  // промежуточная обработка
  render(transform(v));
});

Каждый уровень может добавлять собственную логику, не изменяя исходный поток.

Push-модель данных

Подписочная система Popmotion основана на push-модели:

  • поток сам инициирует передачу значений
  • наблюдатель не запрашивает данные
  • обновления приходят асинхронно по мере изменения состояния

Это отличает модель от pull-подхода, где значения запрашиваются вручную.

Push-модель особенно эффективна в анимации, так как синхронизирует вычисления с кадрами рендеринга.

Поведение подписок в непрерывных потоках

Некоторые потоки Popmotion являются бесконечными:

  • трекинг мыши
  • физические симуляции без остановки
  • отслеживание жестов

В таких потоках complete может никогда не вызываться. Поэтому управление жизненным циклом полностью ложится на механизм отписки.

const track = pointer().start();

const stop = track.subscribe(v => {
  console.log(v.x, v.y);
});

// остановка при необходимости
stop();

Вложенные подписки и их влияние на архитектуру

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

const a = motionA.subscribe(v => {
  motionB.start(v);
});

Такой подход позволяет строить каскадные системы реакций, где каждый поток влияет на следующий.

Однако чрезмерное вложение подписок приводит к усложнению контроля жизненного цикла, поэтому в архитектуре Popmotion предпочтение отдается композиции потоков, а не их ручному связыванию.

Повторное использование потоков

Один и тот же источник может быть повторно подписан после завершения, если он поддерживает повторный запуск.

const anim = tween();

anim.subscribe(v => render(v));

anim.start();
anim.start();

В этом случае каждый запуск создаёт новую последовательность значений, а подписчики остаются привязанными к источнику.

Управление множественными отписками

При работе с несколькими подписками важно агрегировать функции отписки:

const unsub1 = stream.subscribe(handler1);
const unsub2 = stream.subscribe(handler2);

const stopAll = () => {
  unsub1();
  unsub2();
};

Такой подход позволяет централизованно контролировать жизненный цикл всех наблюдателей.

Роль подписок в архитектуре анимационных систем

Подписочная модель Popmotion формирует основу всей реактивной архитектуры библиотеки. Через неё реализуются:

  • tween-анимации
  • spring-физика
  • pointer и gesture tracking
  • value streams
  • композиция последовательностей

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