26. Solid.js vs React
⚔️ Solid.js vs React: Полное сравнение
Заголовок раздела «⚔️ Solid.js vs React: Полное сравнение»Привет! 👋 Solid.js и React решают одну задачу — построение реактивных UI — но делают это кардинально по-разному. Понимание этих различий поможет тебе не только выбрать правильный инструмент, но и лучше понять оба фреймворка.
Думай об этом как о сравнении двух архитектурных подходов: React — это заново перестраивать виртуальную модель здания при каждом изменении, а Solid — точечно заменять нужный кирпич без пересмотра чертежей.
🧠 Ментальная модель
Заголовок раздела «🧠 Ментальная модель»React: компоненты = функции, которые вызываются повторно
Заголовок раздела «React: компоненты = функции, которые вызываются повторно»// Reactfunction Counter() { const [count, setCount] = useState(0);
// ↓ Эта функция ВЫЗЫВАЕТСЯ ЗАНОВО при каждом setCount! console.log('Counter рендерится'); // Логируется при каждом клике
return ( <div> <p>{count}</p> <button onClick={() => setCount(n => n + 1)}>+</button> </div> );}// Проблема: если здесь есть дорогие вычисления — они повторяются!Solid: компонент = функция-фабрика, вызывается один раз
Заголовок раздела «Solid: компонент = функция-фабрика, вызывается один раз»// Solidfunction Counter() { const [count, setCount] = createSignal(0);
// ↓ Эта функция вызывается ОДИН РАЗ при создании компонента console.log('Counter создаётся'); // Логируется только один раз!
// JSX компилируется в прямые DOM-операции с реактивными связями return ( <div> <p>{count()}</p> {/* ← Только этот узел обновляется */} <button onClick={() => setCount(n => n + 1)}>+</button> </div> );}🔄 Хуки vs Примитивы
Заголовок раздела «🔄 Хуки vs Примитивы»| Концепция | React | Solid |
|---|---|---|
| Состояние | useState | createSignal |
| Побочные эффекты | useEffect | createEffect |
| Вычисления | useMemo | createMemo |
| Коллбек | useCallback | нет нужды — функции не пересоздаются |
| Ref | useRef | createSignal или let ref |
| Контекст | useContext | useContext |
| Редьюсер | useReducer | createReducer из @solid-primitives |
Ключевое различие: правила хуков
Заголовок раздела «Ключевое различие: правила хуков»// React — ЖЁСТКИЕ правила хуков:function ReactComponent({ condition }) { // ❌ Хуки нельзя вызывать внутри условий! if (condition) { const [state] = useState(0); // Ошибка! Нарушение правил хуков }
// ❌ Нельзя вызывать в цикле for (let i = 0; i < 3; i++) { const [x] = useState(i); // Ошибка! }
const [count, setCount] = useState(0); // ✅ Только на верхнем уровне}
// Solid — НИКАКИХ правил:function SolidComponent(props) { // ✅ Можно вызывать где угодно! if (props.condition) { const [state, setState] = createSignal(0); // OK }
for (let i = 0; i < 3; i++) { createEffect(() => console.log(i)); // OK }}🔧 Жизненный цикл
Заголовок раздела «🔧 Жизненный цикл»// === React ===function ReactComponent() { // Монтирование useEffect(() => { console.log('Смонтирован'); return () => console.log('Размонтирован'); // Cleanup }, []);
// При изменении prop/state useEffect(() => { console.log('Обновился'); }); // Без зависимостей — каждый рендер
// При изменении конкретного значения useEffect(() => { console.log('count изменился:', count); }, [count]);}
// === Solid ===import { onMount, onCleanup, createEffect } from 'solid-js';
function SolidComponent() { // Монтирование (один раз) onMount(() => { console.log('Смонтирован'); });
// Cleanup при размонтировании onCleanup(() => { console.log('Размонтирован'); });
// При изменении сигнала (автоматическое отслеживание зависимостей!) createEffect(() => { console.log('count изменился:', count()); // Нет массива зависимостей — Solid сам отслеживает });}🏁 Модель рендеринга
Заголовок раздела «🏁 Модель рендеринга»React Render Cycle: 1. state/props изменились 2. Компонент функция вызывается заново 3. Создаётся новое дерево React элементов (VDOM) 4. Reconciler сравнивает старый и новый VDOM (diffing) 5. Минимальные изменения применяются к DOM
Solid Render Cycle: 1. Сигнал изменился 2. Уведомляются ВСЕ подписчики этого сигнала 3. Каждый подписчик (memo/effect/DOM-узел) обновляется напрямую 4. Никакого VDOM, никакого diffing, никакого reconciliation📦 Экосистема и размер бандла
Заголовок раздела «📦 Экосистема и размер бандла»Размер (gzip): React + ReactDOM: ~45 KB Solid: ~7 KB (в 6 раз меньше!)
Производительность (js-framework-benchmark): Solid: ~1.04x (почти нативный JS!) React: ~1.48x (50% overhead над нативным) Vue: ~1.17x Svelte: ~1.06x
Экосистема (2024): React: ⭐⭐⭐⭐⭐ Огромная (создана Meta, 2013) Solid: ⭐⭐⭐ Растущая (2021, 37k stars)
NPM downloads/week: React: ~25 миллионов Solid: ~200 тысяч
Вакансии: React: Тысячи вакансий Solid: Единицы (пока что)🎯 Когда выбрать Solid.js
Заголовок раздела «🎯 Когда выбрать Solid.js»✅ Выбирай Solid, если: - Производительность критична (реал-тайм, большие списки) - Хочешь минимальный bundle size - Любишь чистую реактивность без магии hooks - Строишь с нуля и нет legacy кода - Нет нужды в огромной экосистеме - Изучаешь как работает реактивность
✅ Выбирай React, если: - Нужна огромная экосистема (UI-библиотеки, инструменты) - Команда уже знает React - Нужны React Native или React Server Components - Много legacy кода на React - Важна максимальная найм на рынке труда - Нужна поддержка Meta и большого сообщества🔄 Эквивалентный код: один компонент в двух стилях
Заголовок раздела «🔄 Эквивалентный код: один компонент в двух стилях»// === React версия ===import { useState, useEffect, useMemo, useCallback } from 'react';
function TodoApp() { const [todos, setTodos] = useState([]); const [filter, setFilter] = useState('all'); const [input, setInput] = useState('');
const filtered = useMemo(() => todos.filter(t => filter === 'all' ? true : filter === 'active' ? !t.done : t.done ), [todos, filter] );
const addTodo = useCallback(() => { if (!input.trim()) return; setTodos(prev => [...prev, { id: Date.now(), text: input, done: false }]); setInput(''); }, [input]);
useEffect(() => { document.title = `Todos: ${todos.length}`; }, [todos.length]);
return (/* JSX */);}// === Solid версия ===import { createSignal, createMemo, createEffect } from 'solid-js';
function TodoApp() { const [todos, setTodos] = createSignal([]); const [filter, setFilter] = createSignal('all'); const [input, setInput] = createSignal('');
// useMemo → createMemo (нет зависимостей — автоматически!) const filtered = createMemo(() => todos().filter(t => filter() === 'all' ? true : filter() === 'active' ? !t.done : t.done ) );
// useCallback → просто функция (не пересоздаётся) function addTodo() { if (!input().trim()) return; setTodos(prev => [...prev, { id: Date.now(), text: input(), done: false }]); setInput(''); }
// useEffect → createEffect (нет зависимостей!) createEffect(() => { document.title = `Todos: ${todos().length}`; });
return (/* JSX */);}⚠️ Типичные ловушки перехода с React на Solid
Заголовок раздела «⚠️ Типичные ловушки перехода с React на Solid»// ❌ Реакт-мышление: деструктуризация пропсовfunction Bad({ name, count }) { // Теряет реактивность! return <div>{name}: {count}</div>;}
// ✅ Solid: используй props напрямуюfunction Good(props) { return <div>{props.name}: {props.count}</div>;}
// ❌ Реакт-мышление: читать сигнал вне JSX без createMemofunction Bad2() { const value = mySignal(); // Читается один раз, не реактивно вне контекста return <div>{value}</div>; // Это работает в JSX, но...}
// ❌ Не делай так: ранний выход из компонента при условииfunction Bad3(props) { if (!props.user) return null; // В Solid — это может работать, но... const name = props.user.name; // ... реактивность name может нарушиться return <div>{name}</div>;}
// ✅ Используй Show для условного рендерингаfunction Good3(props) { return ( <Show when={props.user}> {(user) => <div>{user().name}</div>} </Show> );}
// ❌ Эффекты при первом рендереcreateEffect(() => { // В Solid createEffect запускается СИНХРОННО при создании! // Это может быть неожиданно если ты привык к React useEffect fetchData(); // Запустится сразу});
// ✅ Используй onMount для действий только после монтированияonMount(() => { fetchData(); // Только после монтирования в DOM});