Вы можете вычислять зависимые реактивные состояния как можно раньше, так и как можно позже, вплоть до отказа от вычислений, если это возможно.
🍔 Instant: Мгновенные реакции
⏰ Defer: Отложенные реакции
🦥 Lazy: Ленивые вычисления
🍔 Instant: Мгновенные реакции
В библиотеках, таких как RxJS, зависимые состояния пересчитываются сразу же при изменении зависимости. Если нужно изменить несколько состояний подряд, это может привести к ненужным вычислениям.

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

Однако пересчет будет выполнен в любом случае, даже если результат нам не нужен.
🦥 Lazy: Ленивые вычисления
В моделях реактивности с отложенным вычислением возможно ленивое вычисление инвариантов — только в момент, когда зависимое состояние действительно требуется.

При изменении исходных состояний мы не вычисляем зависимые и даже не планируем их вычисление, а лишь помечаем их как устаревшие. И если впоследствии к ним обращаются, они начинают вычисляться.
Это и самый экономный подход, и самый согласованный, так как гарантирует, что при каждом обращении к состоянию результат будет актуальным. Именно поэтому в $mol_wire это является основным принципом работы: на уровне абстракций ты даже не замечаешь, что ресурсы тратятся не сразу, а только по мере необходимости.
Ленивость в $mol_wire
Рассмотрим простое приложение со стабилизированным состоянием сумматоров.

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

Это необходимо, чтобы каждое состояние знало свой статус актуальности в любой момент. Всего таких статусов 4:
🔴 stale (-1) — значение устарело и требует пересчёта (исходное состояние).
🟡 doubt (-2) — среди косвенных зависимостей есть устаревшие значения, их нужно обновить.
🟢 fresh (-3) — значение актуально и может быть возвращено сразу.
🔵 final (-4) — значение больше не изменится (задача выполнена или атом уничтожен).
Статус хранится в поле cursor. Отрицательные значения кодируют текущий статус, а неотрицательные — что в данный момент вычисляется и ведётся учёт зависимостей. Положительное значение cursor означает, сколько издателей уже отслежено.
Распространение уведомлений об изменении статуса к корню приложения может быть ресурсоёмкой операцией, особенно если тысячи других зависят от одного условия. Но это разумная цена за гарантию согласованности.
Если сразу изменить другие состояния, цепочки уведомлений будут короче, так как они наткнутся на уже устаревший подграф.

Пока что мы изменили только статусы состояний. Если по какой-то причине мы захотим прочитать значение устаревшего или сомнительного состояния, вычисления начнутся только в этот момент.

Сначала происходит погружение через сомнительные к устаревшим, которые вычисляют новое значение. Если значение изменилось, все зависимости тоже становятся устаревшими и начинают обновляться. И так вычисления поднимаются к запрошенному значению. Или останавливаются где-то на полпути, и все состояния выше помечаются как актуальные.
Таким образом, несмотря на отложенные ленивые вычисления, мы можем в любой момент получить текущее значение любого состояния, согласованное со всеми косвенными зависимостями.
Наконец, даже если мы явно не запрашивали никакое состояние, происходит автоматическое отложенное обновление всех корней строго в том порядке, в котором они вычислялись изначально.

Это гарантирует, что даже если граф состояний перестраивается во время пересчёта (что случается довольно часто), ни одно состояние не будет просто отброшено после вычисления, так как состояние, влияющее на его существование, всегда будет вычислено раньше.
Поскольку пользователь всё равно не увидит результат чаще, чем с частотой кадров экрана, имеет смысл отложить автоматическое обновление состояний до следующего кадра анимации. Таким образом, все изменения состояний в одном кадре группируются и приводят к одному повторному рендеру. А все пересчёты происходят только при необходимости.
Интересная особенность использования кадров анимации в том, что когда интерфейс не виден, его рендеринг автоматически замораживается, не тратя ресурсы на невидимую работу. Однако некоторые задачи всё равно требуют работы в фоне. Их нужно запускать согласно нужному интервалу времени. Но об этом поговорим в другой раз.
А пока, подписывайтесь на что-нибудь, вступайте во что-то там, и держите руку на пульсе вот этого вот.
