Перейти к содержимому

1. Что такое Solid.js и почему он лучше

Что такое Solid.js: реактивность без Virtual DOM 🚀

Заголовок раздела «Что такое Solid.js: реактивность без Virtual DOM 🚀»

Привет! 👋 Яша здесь. Сегодня мы разберём главный вопрос: а что вообще такое Solid.js и почему он такой особенный? Прежде чем писать код, важно понять архитектуру — это изменит то, как ты думаешь о реактивных UI-фреймворках в целом.

Если коротко: Solid.js — это JavaScript UI-фреймворк, который обновляет DOM хирургически точно, без Virtual DOM, без перерендеринга компонентов, и с сигналами в качестве основного примитива реактивности.


В React при каждом изменении состояния запускается цикл:

setState() → компонент рендерится заново →
создаётся новый виртуальный DOM → сравнение с предыдущим (diffing) →
вычисляются минимальные изменения → применяются к реальному DOM

Это красивая абстракция, но дорогостоящая. Особенно когда компонентов много.

В Solid.js этого цикла нет вообще. Вместо этого:

signal.set(newValue) →
только те DOM-узлы, которые читают этот сигнал, обновляются напрямую

Solid знает заранее, какой DOM-узел зависит от какого сигнала — это определяется во время компиляции JSX.

В React функция компонента — это функция рендеринга. Она вызывается при каждом изменении состояния или пропсов:

// React: эта функция запускается снова и снова
function Counter() {
const [count, setCount] = useState(0);
console.log('Рендер!'); // выводится при каждом изменении count
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

В Solid.js функция компонента — это функция настройки. Она вызывается ровно один раз:

// Solid: функция вызывается ОДИН раз при монтировании
function Counter() {
const [count, setCount] = createSignal(0);
console.log('Настройка!'); // выводится только один раз!
// JSX компилируется в подписки, не в повторный вызов функции
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}

3. JSX компилируется в настоящие DOM-операции

Заголовок раздела «3. JSX компилируется в настоящие DOM-операции»

React компилирует JSX в вызовы React.createElement():

// React: JSX → createElement
<div className="box">{count}</div>
// ↓
React.createElement('div', { className: 'box' }, count)

Solid компилирует JSX в реальные DOM-инструкции:

// Solid: JSX → прямые DOM операции
<div class="box">{count()}</div>
// ↓
const _el = document.createElement('div');
_el.className = 'box';
// Реактивная подписка: обновляет только textNode
createEffect(() => { _el.textContent = count(); });

«Мелкозернистая» (fine-grained) реактивность означает, что обновления происходят на уровне отдельных DOM-узлов, а не компонентов.

Представь компонент с тремя независимыми сигналами:

import { createSignal } from 'solid-js';
function Dashboard() {
const [name, setName] = createSignal('Яша');
const [score, setScore] = createSignal(100);
const [online, setOnline] = createSignal(true);
return (
<div>
<h1>Привет, {name()}!</h1> {/* зависит от name */}
<p>Очки: {score()}</p> {/* зависит от score */}
<span>{online() ? '🟢' : '🔴'}</span> {/* зависит от online */}
</div>
);
}

Когда меняется score, обновляется только текстовый узел внутри <p>. Ни <h1>, ни <span>, ни сама функция Dashboard не пересчитываются. Это принципиально отличается от React, где при любом setScore весь компонент рендерится заново.


Solid.js строится на трёх реактивных примитивах:

import { createSignal } from 'solid-js';
const [count, setCount] = createSignal(0);
// count() — геттер (читает значение)
// setCount(n) — сеттер (записывает значение)
console.log(count()); // 0
setCount(5);
console.log(count()); // 5
// Функциональное обновление (как в React)
setCount(prev => prev + 1);
import { createSignal, createEffect } from 'solid-js';
const [name, setName] = createSignal('Яша');
// Запускается сразу, потом при каждом изменении name()
createEffect(() => {
document.title = 'Привет, ' + name() + '!';
});
setName('Миша'); // title обновится автоматически
import { createSignal, createMemo } from 'solid-js';
const [count, setCount] = createSignal(5);
// Пересчитывается только когда count() меняется
const doubled = createMemo(() => count() * 2);
console.log(doubled()); // 10
setCount(7);
console.log(doubled()); // 14

Главное различие, которое сбивает с толку всех, кто приходит из React:

// React: count — это ЗНАЧЕНИЕ
const [count, setCount] = useState(0);
console.log(count); // 0 (число)
// В JSX: <p>{count}</p>
// Solid: count — это ФУНКЦИЯ (геттер)
const [count, setCount] = createSignal(0);
console.log(count()); // 0 (вызываем как функцию!)
// В JSX: <p>{count()}</p> ← нужны скобки!

Почему геттер — функция? Потому что Solid отслеживает реактивные зависимости через вызовы функций. Когда ты вызываешь count() внутри createEffect или JSX, Solid знает: «этот эффект/DOM-узел зависит от count». Если count() было бы просто значением, отслеживать зависимости было бы невозможно.


Solid — это компилируемый фреймворк. Плагин vite-plugin-solid трансформирует твой JSX в оптимальный JavaScript:

Входной код (что ты пишешь):

function App() {
const [show, setShow] = createSignal(true);
const [text, setText] = createSignal('Привет');
return (
<div>
<button onClick={() => setShow(s => !s)}>Показать/скрыть</button>
<Show when={show()}>
<p>{text()}</p>
</Show>
</div>
);
}

Скомпилированный код (что попадает в браузер):

import { createSignal, createEffect, insert, template } from 'solid-js/web';
const _tmpl$ = template('<div><button>Показать/скрыть</button></div>');
function App() {
const [show, setShow] = createSignal(true);
const [text, setText] = createSignal('Привет');
const _el = _tmpl$.cloneNode(true);
const _el2 = _el.firstChild; // button
_el2.addEventListener('click', () => setShow(s => !s));
// insert — реактивная вставка, подписывается на show()
insert(_el, () => show() && (() => {
const _p = document.createElement('p');
createEffect(() => { _p.textContent = text(); });
return _p;
})(), _el2.nextSibling);
return _el;
}

Видишь? Нет никаких React.createElement. Нет virtual DOM. Только прямые DOM-операции и точечные реактивные подписки.


Solid.js стабильно входит в топ-3 самых быстрых фреймворков по данным js-framework-benchmark:

ОперацияVanilla JSSolid.jsReactVue 3
Создание 1000 строк1.0×1.05×1.6×1.4×
Обновление каждой 10-й строки1.0×1.06×2.1×1.8×
Выделение строки1.0×1.07×1.7×1.6×
Удаление строки1.0×1.05×1.9×1.5×
Размер бандла~7 KB~45 KB~34 KB

Solid работает в ~1.05-1.1× от ванильного JavaScript. Это впечатляет для декларативного фреймворка!


Solid.js хорошо подходит для:

1. Высоконагруженные интерфейсы: Дашборды с real-time данными, редакторы, таблицы с тысячами строк.

2. Приложения с ограниченными ресурсами: Мобильные WebView, встраиваемые виджеты, IoT-интерфейсы.

3. SPA с богатой интерактивностью: Где React начинает «залипать», Solid остаётся плавным.

4. Новые проекты, где нет legacy: Если начинаешь с нуля и хочешь максимальную производительность.

Solid менее подходит для:

  • Команд, глубоко вложенных в React-экосистему
  • Проектов, где критично иметь большой кадровый рынок
  • Случаев, когда нужны специфические React-библиотеки без аналогов

Посмотри наглядно, в чём разница. В левой панели — React-подход: при любом изменении счётчик рендеров растёт. В правой — симуляция Solid-подхода: компонент настраивается один раз, обновляется только нужный DOM-узел: