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

После фиксации контекста владения в версии 2.1.13-beta движок упёрся в финальный рубеж — сложнейший тест #237-COMPUTED-CASCADE. В этом сценарии дочерний эффект объявляется не просто в теле родителя, а динамически рождается внутри ленивой computed-ноды, от которой зависит внешний эффект. В плоской push-модели планировщика при изменении корня графа пуш-волна инвалидации одновременно долетала до computed (переводя его в CHECK) и до вложенного inner-эффекта, так как тот успел считать сигнал напрямую при первом старте. В результате inner-эффект самостоятельно взрывал свой inner_cleanup в пуш-фазе, полностью опережая и разрушая фазу выполнения внешнего эффекта (Received: inner_cleanup -> outer_cleanup).

СХЕМА ТЕСТА #237-COMPUTED-CASCADE (Сквозная вложенность):

[ Signal A ] ─── (прямая паразитная пуш-связь) ───┐
│                                            ▼
(пуш)                                    [ Inner Effect ] (внутри Computed)
│                                            ▲
▼                                            │ (ленивый Pull)
[ Computed C ] ─── (маркер CHECK) ───→ [ Outer Effect ]

Для бескомпромиссного решения этой дилеммы в версии 2.1.14-beta мы применили паттерн Dependency-проксирования (Consumer Scope Isolation). Теперь фабрика createEffect проверяет контекст создания: если её владельцем является вычисляемое свойство (isComputedOwned), глобальный указатель engine.activeConsumer принудительно подменяется на родительский computed-узел. Это аппаратно отсекло вложенный эффект от прямой пуш-связи с сигналом a: сигнал теперь видит своим прямым подписчиком исключительно computed(c), а сам inner-эффект полностью изолируется от глобальной очереди pendingEffects ядра.

В результате граф перешёл на чистую иерархическую ленивую Pull-семантику, принятую в лидирующих мировых движках вроде alien-signals и Preact. При мутации корня a.value = 1 пуш-волна идёт строго по топологической цепочке a ──→ c ──→ outer. Планировщик берёт из очереди outer-эффект, тот первым делом синхронно взводит свой outer_cleanup, затем инициирует ленивую пулл-валидацию родителей, в процессе которой computed заходит в свой пересчёт, каскадно утилизирует старого ребёнка (inner_cleanup) и рождает новое тело (inner_body:1). Порядок вызовов сошёлся в безупречную математическую матрицу: inner_cleanup -> inner_body:1 -> outer_cleanup -> outer_body:1, что закрыло последние падения с сайта.