Skip to content

ContinuumUI, в котором изменение — это данные

Начинается как useState — state(0), .set, значения прямо в JSX. А дальше каждое изменение — происшествие в потоке с транзакционной семантикой, и undo, поиск без гонок и синхронизация вкладок стоят по одной свёртке — а не по библиотеке.

Скорость и память класса 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× по весу.

Тот же счётчик, две модели

import { useState } from "react";

function Counter() {
  // the component re-runs on every render
  const [count, setCount] = useState(0);
  const double = count * 2; // recomputed each render

  return (
    <button onClick={() => setCount((c) => c + 1)}>
      {count} / {double}
    </button>
  );
}

Перевыполняется сверху вниз на каждое изменение; double пересчитывается, дерево диффится.

Попробовать — прямо здесь

Поменяйте код, нажмите 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); //       + undo
onCleanup(persist("todos", todos)); //                            + персист
// событие `storage`, диспатчащее в тот же редьюсер:              + синхронизация вкладок

А поскольку запросы — тоже поток, поиск не умеет гоняться:

tsx
const results = resource(debounce(query.updates, 300), search);
// устаревший ответ физически не может перезаписать свежий

Редьюсер при этом не меняется ни на букву. Книга паттернов проходит всё это — undo, оптимистичные обновления, поиск без гонок — по несколько строк поверх одного потока.