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

3. JSX в Solid: отличия от React

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


🔄 JSX: синтаксический сахар для двух разных систем

Заголовок раздела «🔄 JSX: синтаксический сахар для двух разных систем»

JSX — это не стандартный JavaScript. Это синтаксический сахар, который компилятор превращает в вызовы функций. Но в React и Solid эти вызовы принципиально разные.

// 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:
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-элементы и устанавливает точечные реактивные подписки. Нет объектов, нет сравнения, нет виртуального дерева.


// ❌ React (нужен className из-за зарезервированного слова)
<div className="container">
// ✅ Solid (использует нативный HTML-атрибут)
<div class="container">

Solid работает напрямую с DOM, поэтому использует атрибуты как в HTML.

// ❌ 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: компонент Show
function App() {
const [isLoggedIn, setIsLoggedIn] = createSignal(false);
return (
<div>
<Show when={isLoggedIn()} fallback={<LoginForm />}>
<Dashboard />
</Show>
</div>
);
}

Почему <Show> лучше? Solid компилирует <Show> в оптимальный код, который не пересоздаёт DOM-узлы при повторном появлении. Тернарный оператор тоже работает, но <Show> семантически точнее.


import { For } from 'solid-js';
// React: .map() с key
function 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>

В Solid пропсы — это реактивный объект. Нельзя деструктурировать их напрямую (теряется реактивность):

// ❌ Деструктуризация теряет реактивность!
function Button({ onClick, class: cls, children, disabled }) {
// Если снаружи изменится disabled — здесь ничего не обновится
}
// ✅ Используй splitProps
import { 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:

// Solid поддерживает spread-пропсы
function Input(props) {
return <input {...props} />;
}
// Использование
<Input type="email" value={email()} onInput={e => setEmail(e.target.value)} />

Но при spread нужно соблюдать осторожность: если есть пропсы, которые ты хочешь «поглотить» (не передавать в DOM), используй splitProps перед spread.


// 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 }]} />;

Посмотри, во что компилируется один и тот же JSX в React и Solid: