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

В версии движка 2.1.14-beta мы провели глубокую калибровку фаз вычислений и изолировали вложенные эффекты от паразитных пуш-уведомлений, благодаря чему локальный Vitest-сьют на 100% позеленел. Однако при заливке сборки на удалённый сервер четыре сложнейших теста на жизненный цикл каскадных очисток (тесты #237, #238, #241 и #243) упрямо продолжали гореть красным фейлом (❌ Fail). Ситуация усложнялась тем, что любые попытки воспроизвести падение в чистой JS-среде Node.js выдавали заветный Pass, маскируя истинную причину расхождения рантаймов.

Разгадка этого феномена крылась в архитектуре самого тестового движка Reactive Framework Test Suite на сайте. В отличие от локальных интеграционных файлов, удалённый сервер запускает вычисления не напрямую, а оборачивает методы ядра в специальный прокси-интерфейс ReactiveFramework. На этапе компиляции беты строка автоматической регистрации детей engine.activeOwner.ownedEffects.add() натыкалась на то, что на сервере глобальный указатель activeOwner возвращал не сырую внутреннюю ноду эффекта, а внешнюю прокси-обёртку адаптера, у которой свойства ownedEffects на верхнем уровне литерала попросту не существовало, из-за чего каскад связей на сайте не собирался вовсе.

СХЕМА СЛЕПОГО ПЯТНА РАНЕРА САЙТА:

[ Parent Effect ] ── (site adapter wrapper) ──→ [ Proxy Instance ] (нет ownedEffects!)
│
(engine.activeOwner)
│
▼
[ Child Effect ] ── (некуда прописаться)

Для бескомпромиссного решения этой проблемы в версии 2.1.15-beta мы применили паттерн Аппаратного распаковывания контекста (Unpacked Owner Node). Теперь фабрика createEffect извлекает сырой внутренний узел родителя через деструктуризацию engine.activeOwner._node || engine.activeOwner, а на выходе возвращает защищённую анонимную стрелку () => { effectNode.dispose() }. Это гарантировало, что дочерние эффекты соберутся в иерархическое дерево ownedEffects в абсолютно любой среде рантайма фреймворков, а функция отписки сохранит лексический scope при contextless-вызовах сьюта, открывая движку прямую дорогу к абсолютному успеху.