Начинается как useState — state(0), .set, значения прямо в JSX. А дальше каждое изменение — происшествие в потоке с транзакционной семантикой, и undo, поиск без гонок и синхронизация вкладок стоят по одной свёртке — а не по библиотеке.
Проведите каждое действие пользователя через один поток и сверните его — undo, персист, синхронизация вкладок и поиск без гонок становятся одной дополнительной свёрткой каждый, а не библиотекой каждый. Внешние источники — события storage, сокеты — входят в ту же модель моментов.
Обновления — это транзакции
Одновременные изменения — один атомарный момент; производное значение не может увидеть полсостояния. Это гарантируют законы движка (семантика классического FRP), а не дисциплина.
Нет ре-рендеров
Функция компонента выполняется ровно один раз. Состояние — значение, которое кладётся в JSX; обновляется только зависящий от него узел или атрибут — без хуков, массивов зависимостей и виртуального DOM.
Достаточно маленький, чтобы прочитать
Целое приложение — фреймворк, состояние и ваш код — собирается в 5,6 кБ gzip JavaScript; одни React + ReactDOM в ~8 раз больше. Жёсткие бюджеты размера проверяются в CI.
Скорость и память класса Solid — в разы меньше по весу
Размер бандла — счётчик целикомкБ gzip · меньше — лучше
Continuum5.6 кБ
Solid~7 кБ
React + ReactDOM~45 кБ
Память — 10 000 строк, heap после GCМБ · меньше — лучше
Continuum9.7 МБ
Solid14.1 МБ
Мусор GC — создание 10 000 строкМБ выделено · меньше — лучше
Continuum8.4 МБ
Solid13.8 МБ
−32 % памяти, ~40 % меньше мусора, быстрее интерактив — и втрое меньше загрузка. Замер на одной машине через один и тот же Playwright-харнесс (npm run bench, bench:mem, size); полная таблица с потактовыми таймингами — в обзоре. React — модель виртуального DOM с ре-рендером: в разы медленнее на той же таблице и ~8× по весу.
Поменяйте код, нажмите Run, откройте вкладку Compiled — увидите, как он превращается в шаблон, разбираемый один раз, с единственной дыркой-вставкой. Затем создайте свой проект:
bash
npm create continuum-js@latest my-app
Если вы знаете useState, вы уже знаете и это — с одной разницей: Counter больше никогда не выполнится. {count} привязывает текстовый узел к значению; клик патчит ровно этот узел.
Всё, что выше, обещает и Solid. Вот часть, которой у него нет: само изменение — это значение. Проведите каждое изменение через один поток действий и сверните его — и фичи, на которые обычно берут по библиотеке, становятся одной строкой каждая:
tsx
const actions = stream<Action>();const todos = actions.accum(loadPersisted("todos", []), reduce); // состояниеconst history = actions.accum(emptyHistory, undoReduce); // + undoonCleanup(persist("todos", todos)); // + персист// событие `storage`, диспатчащее в тот же редьюсер: + синхронизация вкладок
А поскольку запросы — тоже поток, поиск не умеет гоняться:
tsx
const results = resource(debounce(query.updates, 300), search);// устаревший ответ физически не может перезаписать свежий
Редьюсер при этом не меняется ни на букву. Книга паттернов проходит всё это — undo, оптимистичные обновления, поиск без гонок — по несколько строк поверх одного потока.