3. JSX в Solid: отличия от React
JSX в Solid.js: компиляция без Virtual DOM 🔧
Заголовок раздела «JSX в Solid.js: компиляция без Virtual DOM 🔧»Привет! 👋 Яша здесь. Сегодня разберём самую важную техническую особенность Solid.js — как JSX компилируется в реальные DOM-операции. Если ты приходишь из React, тебя ждёт сюрприз: синтаксис похожий, но под капотом — совершенно другое!
🔄 JSX: синтаксический сахар для двух разных систем
Заголовок раздела «🔄 JSX: синтаксический сахар для двух разных систем»JSX — это не стандартный JavaScript. Это синтаксический сахар, который компилятор превращает в вызовы функций. Но в React и Solid эти вызовы принципиально разные.
React: JSX → React.createElement()
Заголовок раздела «React: JSX → React.createElement()»// React JSX:const element = <div class="box">{count}</div>;
// Компилируется в:const element = React.createElement('div', { className: 'box' }, count);// Результат: JavaScript-объект (виртуальный DOM-узел)// { type: 'div', props: { className: 'box', children: count } }React создаёт объекты — виртуальный DOM. Эти объекты потом сравниваются с предыдущей версией, и из разницы вычисляются изменения реального DOM.
Solid: JSX → прямые DOM-инструкции
Заголовок раздела «Solid: JSX → прямые DOM-инструкции»// Solid JSX:const element = <div class="box">{count()}</div>;
// Компилируется примерно в:const _tmpl$ = /*#__PURE__*/ template('<div class="box"> </div>');
function MyComponent() { const _el = _tmpl$.cloneNode(true); const _el$text = _el.firstChild;
// Реактивная подписка: обновляет ТОЛЬКО этот текстовый узел createEffect(() => { _el$text.data = count(); });
return _el;}Solid создаёт настоящие DOM-элементы и устанавливает точечные реактивные подписки. Нет объектов, нет сравнения, нет виртуального дерева.
🎯 Ключевые отличия JSX в Solid
Заголовок раздела «🎯 Ключевые отличия JSX в Solid»1. class вместо className
Заголовок раздела «1. class вместо className»// ❌ React (нужен className из-за зарезервированного слова)<div className="container">
// ✅ Solid (использует нативный HTML-атрибут)<div class="container">Solid работает напрямую с DOM, поэтому использует атрибуты как в HTML.
2. for вместо htmlFor в <label>
Заголовок раздела «2. for вместо htmlFor в <label>»// ❌ React<label htmlFor="email">Email</label><input id="email" />
// ✅ Solid<label for="email">Email</label><input id="email" />3. Обработчики событий — onClick, не onClick={handler}
Заголовок раздела «3. Обработчики событий — onClick, не onClick={handler}»// React и Solid: синтаксис одинаковый<button onClick={() => setCount(c => c + 1)}>+</button>
// Но под капотом Solid делает:button.addEventListener('click', () => setCount(c => c + 1));// А не через синтетические события React!4. Реактивные значения — функции, не значения
Заголовок раздела «4. Реактивные значения — функции, не значения»Это самое важное различие в JSX:
// React: count — это значениеconst [count, setCount] = useState(0);return <p>{count}</p>; // просто вставляем значение
// Solid: count — это ФУНКЦИЯ (геттер сигнала)const [count, setCount] = createSignal(0);return <p>{count()}</p>; // вызываем как функцию!Почему это важно: когда Solid компилирует {count()}, он знает, что нужно создать реактивную подписку. Если ты забудешь скобки и напишешь {count}, Solid подставит функцию как строку — это баг!
// ❌ Забыл скобки — в DOM будет "() => ..."<p>{count}</p>
// ✅ Правильно — DOM обновляется при изменении сигнала<p>{count()}</p>📋 Компонент <Show> вместо тернарного оператора
Заголовок раздела «📋 Компонент <Show> вместо тернарного оператора»В React условный рендеринг делается через обычные JS-выражения:
// React: тернарный операторreturn <div>{isLoading ? <Spinner /> : <Content />}</div>;
// React: &&return <div>{isLoggedIn && <Dashboard />}</div>;В Solid это работает, но рекомендуется использовать встроенный компонент <Show>:
import { Show } from 'solid-js';
// Solid: компонент Showfunction App() { const [isLoggedIn, setIsLoggedIn] = createSignal(false);
return ( <div> <Show when={isLoggedIn()} fallback={<LoginForm />}> <Dashboard /> </Show> </div> );}Почему <Show> лучше? Solid компилирует <Show> в оптимальный код, который не пересоздаёт DOM-узлы при повторном появлении. Тернарный оператор тоже работает, но <Show> семантически точнее.
📋 Компонент <For> вместо .map()
Заголовок раздела «📋 Компонент <For> вместо .map()»import { For } from 'solid-js';
// React: .map() с keyfunction List({ items }) { return ( <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> );}
// Solid: компонент For (без key!)function List({ items }) { return ( <ul> <For each={items()}> {(item) => <li>{item.name}</li>} </For> </ul> );}Ключевые отличия:
- Нет
keyпроп! Solid отслеживает элементы по значению/ссылке автоматически eachпринимает реактивный массив (из сигнала)- Callback-функция получает сам элемент и геттер индекса
// Доступ к индексу в For:<For each={items()}> {(item, index) => ( <li>{index() + 1}. {item.name}</li> // index — это функция! index(), не просто index )}</For>🔀 splitProps — разделение пропсов
Заголовок раздела «🔀 splitProps — разделение пропсов»В Solid пропсы — это реактивный объект. Нельзя деструктурировать их напрямую (теряется реактивность):
// ❌ Деструктуризация теряет реактивность!function Button({ onClick, class: cls, children, disabled }) { // Если снаружи изменится disabled — здесь ничего не обновится}
// ✅ Используй splitPropsimport { splitProps } from 'solid-js';
function Button(props) { // Разделяем: наши пропсы + пропсы для нативного элемента const [local, others] = splitProps(props, ['class', 'children']);
return ( <button class={'btn ' + (local.class || '')} {...others} // передаём onClick, disabled и т.д. > {local.children} </button> );}splitProps создаёт реактивные прокси — изменения во внешних пропсах автоматически отражаются внутри компонента.
🧩 Spread-пропсы
Заголовок раздела «🧩 Spread-пропсы»Передача всех атрибутов через spread:
// Solid поддерживает spread-пропсыfunction Input(props) { return <input {...props} />;}
// Использование<Input type="email" value={email()} onInput={e => setEmail(e.target.value)} />Но при spread нужно соблюдать осторожность: если есть пропсы, которые ты хочешь «поглотить» (не передавать в DOM), используй splitProps перед spread.
🛠️ JSX-специфичные атрибуты Solid
Заголовок раздела «🛠️ JSX-специфичные атрибуты Solid»// ref — получение ссылки на DOM-элементlet inputRef: HTMLInputElement;<input ref={inputRef} />;// После монтирования inputRef === реальный DOM-элемент
// classList — удобный способ управления классами<div classList={{ 'active': isActive(), 'disabled': isDisabled(), 'highlighted': isSelected(),}} />;
// style как объект (как в React)<div style={{ color: 'red', 'font-size': '16px', // CSS-свойства в kebab-case как строки}} />;
// innerHTML и innerText (осторожно! XSS-уязвимость)<div innerHTML={trustedHtml()} />;
// on:event — стандартные DOM-события<div on:click={handler} />; // эквивалент addEventListener
// on:event с опциями (capture, once, passive)<div on:click={[handler, { once: true }]} />;🎮 Playground: визуализатор компиляции JSX
Заголовок раздела «🎮 Playground: визуализатор компиляции JSX»Посмотри, во что компилируется один и тот же JSX в React и Solid: