RS

Headless UI-движок: разделение логики и рендера в SkyGraph

Invalid Date мин чтения

SkyGraph — моя UI-библиотека для React. Изначально я делал её "обычной", как shadcn или MUI: компонент состоит из пропсов, внутреннего состояния и JSX. Через год понял что это тупик.

Почему обычная архитектура — тупик

Когда нужна сложная фича (виртуализация таблицы на 100к строк, drag-and-drop в дереве, валидация формы с условными полями), внутри компонента появляется логика, которую невозможно ни переиспользовать, ни нормально протестировать.

Тесты на JSX-компонент = тесты на DOM, что медленно, хрупко и не учит ничему важному.

Решение — headless

Headless значит: вся логика живёт отдельно от рендера. Компонент только подписывается на состояние и рисует.

Структура SkyGraph:

@skygraph/core    — reactive runtime + 7 engines
@skygraph/react   — React-обёртки над core
@skygraph/vue     — будущая Vue-обёртка

Reactive runtime

В основе — самописный reactive runtime похожий на Solid: signal, effect, computed. Без проксирования объектов, без VirtualDOM. Любая часть состояния — это signal, на который можно подписаться.

const count = signal(0);
effect(() => {
  console.log("count:", count());
});
count.set(5);

Engines

Поверх runtime — 7 движков. Каждый — независимая абстракция со своим API:

  • CoreEngine — общая шина событий, middleware, persistence
  • FormEngine — валидация полей, dirty/touched, asynchronous validation
  • TableEngine — сортировка, фильтрация, выделение, виртуализация
  • TreeEngine — раскрытие узлов, drag-drop, lazy loading
  • VirtualEngine — виртуализация рендера (общий движок для Table/List)
  • GraphEngine — графы, узлы, рёбра, layout-алгоритмы
  • CalendarEngine — date-математика, диапазоны, timezones

Почему это работает

Когда нужно протестировать поведение таблицы на 100к строк — пишешь тест на TableEngine без рендера. Запускается за 5мс. Когда нужно перевести библиотеку на Vue — пишешь обёртки, ядро не трогаешь.

Тестовое покрытие выросло до 85%, из них 90% — тесты на ядро, без DOM.