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

15. Обработка событий

Яша, события в Solid.js — это целая философия. На первый взгляд похоже на React: onClick, onChange… Но за этим скрывается принципиально другой механизм делегирования, нативные события через on:click, модификаторы и кое-что, чего в React нет вообще! Разберёмся по порядку 🚀


В Solid есть два синтаксиса для событий:

// Делегированное событие (camelCase) — добавляется на document
<button onClick={handleClick}>Клик</button>
// Нативное событие (on: + lowercase) — addEventListener напрямую
<button on:click={handleClick}>Нативный клик</button>

Разница принципиальная:

СвойствоonClickon:click
МеханизмДелегирование на documentaddEventListener на элементе
ПроизводительностьЛучше для множества элементовЧуть медленнее
BubblingДелегируется через DOMНативное всплытие
stopPropagationВлияет на Solid-делегаторНативный стоп
Кастомные события❌ Не работает✅ Работает
// Список из 1000 элементов — один обработчик на document!
<For each={items()}>
{(item) => (
<div onClick={() => select(item.id)}>
{item.name}
</div>
)}
</For>
// Кастомное событие — только нативный синтаксис
<MyComponent on:customEvent={(e) => console.log(e.detail)} />

Solid использует одну точку делегирования на document, а не на каждом элементе. Это делает создание и удаление элементов очень быстрым:

// Под капотом Solid делает что-то вроде:
document.addEventListener('click', (e) => {
let target = e.target;
while (target) {
const handler = target.__solid$click;
if (handler) handler(e);
target = target.parentNode;
}
});
// А твой onClick просто записывается как:
// element.__solid$click = handler

Почему это быстро? При создании 1000 <div onClick={...}> Solid не вешает 1000 обработчиков на DOM. Один обработчик на document — и всё!


Solid поддерживает модификаторы через двоеточие в названии события:

// :once — обработчик вызовется только один раз
<button on:click:once={() => console.log('Только раз!')}>
Нажми меня
</button>
// :capture — фаза захвата, не всплытия
<div on:click:capture={(e) => console.log('Сначала я!')}>
<button onClick={() => console.log('Потом я')}>Клик</button>
</div>
// :passive — оптимизация для scroll/touch (не вызываем preventDefault)
<div on:scroll:passive={handleScroll} style="overflow: auto">
{/* Плавный скролл без блокировки */}
</div>
// :once:capture — комбинируй!
<button on:click:once:capture={handler}>Раз и навсегда</button>

С нативным синтаксисом on: можно слушать кастомные события через CustomEvent:

// Компонент, диспатчащий кастомное событие
function Counter() {
const [count, setCount] = createSignal(0);
return (
<button
onClick={() => {
const next = count() + 1;
setCount(next);
// Диспатчим кастомное событие
const event = new CustomEvent('countChange', {
detail: { count: next },
bubbles: true,
});
// Нужна ссылка на элемент
}}
>
{count()}
</button>
);
}
// Родитель слушает через on:
function Parent() {
return (
<div on:countChange={(e: CustomEvent) => {
console.log('Новый count:', e.detail.count);
}}>
<Counter />
</div>
);
}

function SearchBox() {
const [query, setQuery] = createSignal('');
const handleKeyDown = (e: KeyboardEvent) => {
// Горячие клавиши
if (e.key === 'Escape') {
setQuery('');
(e.target as HTMLInputElement).blur();
}
if (e.key === 'Enter') {
search(query());
}
// Ctrl+A — выделить всё
if (e.ctrlKey && e.key === 'a') {
e.preventDefault();
(e.target as HTMLInputElement).select();
}
};
return (
<input
value={query()}
onInput={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Поиск... (Esc — очистить)"
/>
);
}

// REACT:
// onChange вызывается при каждом нажатии клавиши
<input onChange={(e) => setValue(e.target.value)} />
// SOLID:
// onChange — только после потери фокуса (нативный change!)
// onInput — при каждом вводе
<input onChange={(e) => console.log('Потерял фокус')} />
<input onInput={(e) => setValue(e.target.value)} /> // аналог React onChange
// REACT:
// Синтетические события (event pooling — устарело в React 17)
function ReactHandler(e: React.MouseEvent) {
e.persist(); // раньше нужно было
setTimeout(() => console.log(e.type)); // OK в React 17+
}
// SOLID:
// Нативные DOM события, никаких синтетических обёрток
function SolidHandler(e: MouseEvent) {
setTimeout(() => console.log(e.type)); // всегда работает
}

// ⚠️ Осторожно: stopPropagation в делегированных событиях
function Tricky() {
return (
<div onClick={() => console.log('Родитель')}>
<button onClick={(e) => {
e.stopPropagation(); // Останавливает делегатор Solid, не DOM
console.log('Кнопка');
}}>
Кнопка
</button>
</div>
);
}
// Если нужно остановить нативное всплытие — используй on:click
<button on:click={(e) => {
e.stopPropagation(); // Настоящий stopPropagation
console.log('Нативный стоп');
}}>
Нативная кнопка
</button>