1. Что такое Solid.js и почему он лучше
Что такое Solid.js: реактивность без Virtual DOM 🚀
Заголовок раздела «Что такое Solid.js: реактивность без Virtual DOM 🚀»Привет! 👋 Яша здесь. Сегодня мы разберём главный вопрос: а что вообще такое Solid.js и почему он такой особенный? Прежде чем писать код, важно понять архитектуру — это изменит то, как ты думаешь о реактивных UI-фреймворках в целом.
Если коротко: Solid.js — это JavaScript UI-фреймворк, который обновляет DOM хирургически точно, без Virtual DOM, без перерендеринга компонентов, и с сигналами в качестве основного примитива реактивности.
🧠 Архитектура: три ключевые идеи
Заголовок раздела «🧠 Архитектура: три ключевые идеи»1. Нет Virtual DOM
Заголовок раздела «1. Нет Virtual DOM»В React при каждом изменении состояния запускается цикл:
setState() → компонент рендерится заново →создаётся новый виртуальный DOM → сравнение с предыдущим (diffing) →вычисляются минимальные изменения → применяются к реальному DOMЭто красивая абстракция, но дорогостоящая. Особенно когда компонентов много.
В Solid.js этого цикла нет вообще. Вместо этого:
signal.set(newValue) →только те DOM-узлы, которые читают этот сигнал, обновляются напрямуюSolid знает заранее, какой DOM-узел зависит от какого сигнала — это определяется во время компиляции JSX.
2. Компоненты запускаются один раз
Заголовок раздела «2. Компоненты запускаются один раз»В 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';// Реактивная подписка: обновляет только textNodecreateEffect(() => { _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 строится на трёх реактивных примитивах:
createSignal — хранилище значения
Заголовок раздела «createSignal — хранилище значения»import { createSignal } from 'solid-js';
const [count, setCount] = createSignal(0);// count() — геттер (читает значение)// setCount(n) — сеттер (записывает значение)
console.log(count()); // 0setCount(5);console.log(count()); // 5
// Функциональное обновление (как в React)setCount(prev => prev + 1);createEffect — реакция на изменения
Заголовок раздела «createEffect — реакция на изменения»import { createSignal, createEffect } from 'solid-js';
const [name, setName] = createSignal('Яша');
// Запускается сразу, потом при каждом изменении name()createEffect(() => { document.title = 'Привет, ' + name() + '!';});
setName('Миша'); // title обновится автоматическиcreateMemo — кэшированное вычисление
Заголовок раздела «createMemo — кэшированное вычисление»import { createSignal, createMemo } from 'solid-js';
const [count, setCount] = createSignal(5);
// Пересчитывается только когда count() меняетсяconst doubled = createMemo(() => count() * 2);
console.log(doubled()); // 10setCount(7);console.log(doubled()); // 14🔄 Сигналы vs useState
Заголовок раздела «🔄 Сигналы vs useState»Главное различие, которое сбивает с толку всех, кто приходит из 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 JS | Solid.js | React | Vue 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-библиотеки без аналогов
🎮 Playground: React рендеры vs Solid-стиль
Заголовок раздела «🎮 Playground: React рендеры vs Solid-стиль»Посмотри наглядно, в чём разница. В левой панели — React-подход: при любом изменении счётчик рендеров растёт. В правой — симуляция Solid-подхода: компонент настраивается один раз, обновляется только нужный DOM-узел: