Модель подписок в 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 — состояние на текущем кадре
});
В анимационных потоках значение обычно содержит:
Таким образом, подписка становится универсальным способом реагирования на изменения состояния во времени.
Один и тот же поток может иметь несколько независимых подписчиков. Каждый из них получает одинаковую последовательность значений.
const stream = motion;
stream.subscribe(v => {
console.log('логирование:', v);
});
stream.subscribe(v => {
render(v);
});
Подписчики не влияют друг на друга. Поток не дублирует вычисления для каждого подписчика — вычисления происходят один раз, затем результаты распространяются всем наблюдателям.
Помимо передачи значений, поток поддерживает два дополнительных сигнала управления:
complete вызывается, когда поток естественным образом
заканчивается. Это характерно для:
motion.subscribe({
next: v => {},
complete: () => {
console.log('анимация завершена');
}
});
Завершение является однократным событием и всегда происходит после
последнего next.
error сигнализирует о некорректном состоянии
выполнения:
motion.subscribe({
error: (err) => {
console.log('ошибка потока', err);
}
});
После вызова error поток прекращает дальнейшую эмиссию
значений и считается завершённым.
Подписки в Popmotion тесно связаны с управлением производительностью. Каждый активный наблюдатель потенциально удерживает ресурсы, поэтому отписка становится критическим элементом архитектуры.
Особенно это важно в сценариях:
Типичный паттерн использования:
const stop = motion.subscribe(update);
setTimeout(() => {
stop();
}, 2000);
Такой подход предотвращает накопление неиспользуемых подписок.
Popmotion позволяет строить составные потоки, где один поток зависит от другого. Подписки в этом случае становятся связующим механизмом между уровнями абстракции.
const spring = createSpring();
const enhanced = spring.subscribe(v => {
// промежуточная обработка
render(transform(v));
});
Каждый уровень может добавлять собственную логику, не изменяя исходный поток.
Подписочная система 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 формирует основу всей реактивной архитектуры библиотеки. Через неё реализуются:
Каждый из этих элементов сводится к единому принципу: источник данных генерирует значения во времени, а подписчики реагируют на них без прямого вмешательства в логику генерации.