15. Обработка событий
🎯 Обработка событий в Solid.js
Заголовок раздела «🎯 Обработка событий в Solid.js»Яша, события в Solid.js — это целая философия. На первый взгляд похоже на React: onClick, onChange… Но за этим скрывается принципиально другой механизм делегирования, нативные события через on:click, модификаторы и кое-что, чего в React нет вообще! Разберёмся по порядку 🚀
🔧 Делегированные vs Нативные события
Заголовок раздела «🔧 Делегированные vs Нативные события»В Solid есть два синтаксиса для событий:
// Делегированное событие (camelCase) — добавляется на document<button onClick={handleClick}>Клик</button>
// Нативное событие (on: + lowercase) — addEventListener напрямую<button on:click={handleClick}>Нативный клик</button>Разница принципиальная:
| Свойство | onClick | on:click |
|---|---|---|
| Механизм | Делегирование на document | addEventListener на элементе |
| Производительность | Лучше для множества элементов | Чуть медленнее |
| 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
Заголовок раздела «⚡ Делегирование событий в Solid»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
Заголовок раздела «🔄 Отличия от React»// 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
Заголовок раздела «🌊 Делегирование и stopPropagation»// ⚠️ Осторожно: 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>