SolidJS: по-настоящему реактивный JavaScript-фреймворк

2 мин

1. «Святой Грааль» фронтенд-фреймворков: производительность и удобство

С тех пор как React популяризировал виртуальный DOM (Virtual DOM), VDOM словно стал обязательной частью современных фронтенд-фреймворков. Он хранит в памяти виртуальное представление UI и с помощью алгоритма diff вычисляет минимально необходимые обновления DOM, улучшая опыт разработки и производительность в большинстве сценариев.

Но VDOM не обходится бесплатно. Он занимает память, а процесс diff требует вычислительных ресурсов. Отсюда возникает вопрос: можно ли обойти VDOM и точно применять изменения состояния непосредственно к DOM, сохранив при этом декларативный, похожий на React опыт разработки?

SolidJS даёт свой ответ.

2. Основа SolidJS: мелкозернистая реактивность (Fine-Grained Reactivity)

SolidJS — декларативный реактивный JavaScript-фреймворк. Он использует JSX и внешне очень похож на React, однако его внутренние принципы совершенно иные.

Компилятор SolidJS преобразует JSX-код в оптимальные нативные операции с DOM. Вместо VDOM он создаёт граф зависимостей из реактивных «сигналов» (Signals). Когда значение сигнала меняется, повторно выполняются только подписанные на него «эффекты» (Effects) или вычисления (Memos).

Модель, меняющая привычное мышление: компонент выполняется только один раз!

В React при изменении state или props вся функция компонента выполняется повторно. В SolidJS функция компонента выполняется от начала до конца только один раз.

import { createSignal } from 'solid-js';

function Counter() {
  console.log('Component function runs!'); // 这句话只会在组件挂载时打印一次

  const [count, setCount] = createSignal(0);

  const increment = () => setCount(count() + 1);

  return (
    <button type="button" onClick={increment}>
      Count: {count()}
    </button>
  );
}

При нажатии на кнопку обновляются только считыватель сигнала count() и зависящий от него текстовый DOM-узел. Сама функция Counter повторно не запускается. Такие «хирургически точные» обновления и обеспечивают исключительную производительность SolidJS.

3. Основные API

Реактивная система SolidJS в основном состоит из трёх базовых примитивов:

  • createSignal(initialValue): Создаёт реактивный сигнал и возвращает getter и setter: [count, setCount].
  • createEffect(() => {}): Создаёт «эффект», который автоматически отслеживает все прочитанные внутри него сигналы и выполняется снова при изменении любого из них. Отлично подходит для побочных эффектов, например ручной работы с DOM.
  • createMemo(() => {}): Создаёт кэшируемое производное вычисляемое значение. Оно пересчитывается только при изменении внутренних зависимостей-сигналов.
import { createSignal, createEffect } from 'solid-js';

function App() {
  const [firstName, setFirstName] = createSignal('John');
  const [lastName, setLastName] = createSignal('Smith');

  // createEffect 会在 firstName 或 lastName 变化时自动运行
  createEffect(() => {
    console.log(`Full name: ${firstName()} ${lastName()}`);
  });

  // ...
}

4. Почему стоит выбрать SolidJS?

  • Исключительная производительность: SolidJS часто занимает верхние позиции в независимых тестах, а его производительность очень близка к нативному JavaScript-коду.
  • Очень маленький размер пакета: Благодаря отсутствию VDOM runtime основной пакет имеет небольшой размер.
  • Знакомый опыт разработки: Если вы знакомы с React Hooks, то сможете быстро освоить SolidJS. Сочетание JSX и реактивных примитивов одновременно мощное и понятное.
  • Настоящая реактивность: Его модель ближе к MobX или Composition API во Vue, но компилятор позволяет реализовать её без затрат во время выполнения.

Заключение

SolidJS представляет важное направление развития фронтенд-фреймворков: компилятор выполняет больше работы во время сборки, чтобы сократить объём кода во время выполнения и повысить производительность. Он доказывает, что от ограничений виртуального DOM можно полностью отказаться, не жертвуя декларативным опытом разработки. Для приложений, стремящихся к максимальной производительности, SolidJS предлагает крайне привлекательный вариант.