6. События и диспетчеризация
События в Svelte: on:click и createEventDispatcher 🎯
Заголовок раздела «События в Svelte: on:click и createEventDispatcher 🎯»Яша, давай разберёмся с событиями в Svelte — это одна из тех вещей, которые делают Svelte таким приятным для работы! По сравнению с React, где нужно писать onClick={handler}, Svelte использует специальную директиву on: которая читается почти как обычный HTML. Плюс — мощные модификаторы, которые избавляют от необходимости писать e.preventDefault() руками. Поехали! 🚀
🖱️ Обработчики DOM событий
Заголовок раздела «🖱️ Обработчики DOM событий»В Svelte для подписки на DOM-события используется директива on:имяСобытия={обработчик}. Синтаксис максимально читаемый:
<script> function handleClick() { alert('Кнопка нажата!') }
function handleInput(event) { console.log('Введено:', event.target.value) }
function handleKeydown(event) { if (event.key === 'Enter') { console.log('Enter нажат!') } }</script>
<!-- Клик --><button on:click={handleClick}>Нажми меня</button>
<!-- Ввод текста --><input on:input={handleInput} placeholder="Введи текст..." />
<!-- Клавиатура --><input on:keydown={handleKeydown} placeholder="Нажми Enter" />Обрати внимание: обработчик передаётся без скобок handleClick, а не handleClick(). Если добавишь скобки — функция вызовется при рендере, а не по клику!
Svelte поддерживает все стандартные DOM-события: on:click, on:input, on:change, on:keydown, on:keyup, on:keypress, on:focus, on:blur, on:mouseenter, on:mouseleave, on:mousemove, on:submit, on:scroll, и многие другие.
✍️ Инлайн обработчики
Заголовок раздела «✍️ Инлайн обработчики»Иногда удобно написать обработчик прямо в шаблоне — для простых операций это отлично работает:
<script> let count = 0 let name = ''</script>
<!-- Инлайн: простое выражение --><button on:click={() => count++}> Счётчик: {count}</button>
<!-- Инлайн с аргументом события --><button on:click={(e) => console.log('Target:', e.target)}> Залогировать</button>
<!-- Инлайн для инпута --><input on:input={(e) => (name = e.target.value)} placeholder="Введи имя"/><p>Привет, {name || 'незнакомец'}!</p>💡 Совет Яши: Инлайн-обработчики удобны для простых действий вроде
count++или обновления одной переменной. Для более сложной логики лучше выносить в отдельную функцию — код читается чище!
📝 Объект события и TypeScript
Заголовок раздела «📝 Объект события и TypeScript»Когда ты используешь TypeScript (а ты же используешь TypeScript, правда? 😄), Svelte позволяет типизировать объект события:
<script lang="ts"> function handleClick(event: MouseEvent) { console.log('Позиция клика:', event.clientX, event.clientY) const target = event.target as HTMLButtonElement console.log('Текст кнопки:', target.textContent) }
function handleInput(event: Event) { const input = event.target as HTMLInputElement console.log('Значение:', input.value) }
function handleKeydown(event: KeyboardEvent) { console.log('Нажатая клавиша:', event.key) console.log('С Ctrl?', event.ctrlKey) console.log('С Shift?', event.shiftKey) }
function handleSubmit(event: SubmitEvent) { event.preventDefault() const form = event.target as HTMLFormElement const data = new FormData(form) console.log('Имя:', data.get('name')) }</script>
<button on:click={handleClick}>Кликни и посмотри координаты</button>
<input on:input={handleInput} placeholder="Типизированный ввод" />
<input on:keydown={handleKeydown} placeholder="Типизированная клавиатура" />
<form on:submit={handleSubmit}> <input name="name" placeholder="Введи имя" /> <button type="submit">Отправить</button></form>Типы событий, которые тебе чаще всего понадобятся:
MouseEvent— дляclick,mouseenter,mouseleave,mousemoveKeyboardEvent— дляkeydown,keyup,keypressInputEventилиEvent— дляinputFocusEvent— дляfocus,blurSubmitEvent— дляsubmitWheelEvent— дляwheelTouchEvent— для сенсорных событий
🛡️ Модификаторы событий
Заголовок раздела «🛡️ Модификаторы событий»Вот где Svelte блистает! Модификаторы добавляются через символ | сразу после имени события и встроенно выполняют типичные операции с событием. Больше не нужно писать e.preventDefault() в каждом обработчике!
|preventDefault — блокирует стандартное поведение
Заголовок раздела «|preventDefault — блокирует стандартное поведение»<!-- Без модификатора: --><form on:submit={(e) => { e.preventDefault(); handleSubmit() }}>...</form>
<!-- С модификатором: --><form on:submit|preventDefault={handleSubmit}>...</form>
<!-- Ссылка, которая не переходит по URL: --><a href="https://example.com" on:click|preventDefault={() => alert('Переход заблокирован!')}> Не уходи!</a>|stopPropagation — останавливает всплытие
Заголовок раздела «|stopPropagation — останавливает всплытие»<div on:click={() => console.log('Внешний div')}> <!-- Без модификатора — родитель тоже получит клик --> <button on:click={() => console.log('Кнопка')}>Без модификатора</button>
<!-- С модификатором — клик не всплывёт до div --> <button on:click|stopPropagation={() => console.log('Только кнопка')}> С |stopPropagation </button></div>|once — срабатывает только один раз
Заголовок раздела «|once — срабатывает только один раз»<script> let welcomed = false
function showWelcome() { welcomed = true }</script>
<!-- После первого клика слушатель автоматически удаляется --><button on:click|once={showWelcome}> {welcomed ? '✅ Уже поприветствовал!' : 'Поприветствуй меня (один раз)'}</button>💡 Очень полезно для кнопок “Принять условия”, туториалов, и любых действий, которые должны произойти ровно один раз.
|passive — подсказка браузеру для оптимизации
Заголовок раздела «|passive — подсказка браузеру для оптимизации»<!-- Сообщаем браузеру что обработчик НЕ вызовет preventDefault --><!-- Браузер может оптимизировать прокрутку, не дожидаясь обработчика --><div on:scroll|passive={handleScroll}> Скроллируемый контент...</div>
<!-- Особенно полезно для touch-событий --><div on:touchmove|passive={handleTouchMove}> Свайпаемый элемент...</div>|passive не изменяет поведение события — он просто обещает браузеру, что preventDefault() вызван не будет. Это улучшает производительность прокрутки на мобильных устройствах!
|capture — фаза захвата вместо всплытия
Заголовок раздела «|capture — фаза захвата вместо всплытия»<!-- Обработчик срабатывает на фазе захвата (capture phase) --><!-- Это происходит до того, как событие доходит до цели --><div on:click|capture={() => console.log('Поймано на пути вниз!')}> <button on:click={() => console.log('Достигло цели')}> Кнопка </button></div><!-- Порядок: "Поймано на пути вниз!" → "Достигло цели" -->|trusted — только реальные пользовательские события
Заголовок раздела «|trusted — только реальные пользовательские события»<script> function handleRealClick() { console.log('Это настоящий клик пользователя!') }</script>
<!-- Срабатывает только если event.isTrusted === true --><!-- Программные вызовы button.click() будут проигнорированы --><button on:click|trusted={handleRealClick}> Только реальный клик</button>|self — только если target === currentTarget
Заголовок раздела «|self — только если target === currentTarget»<!-- Срабатывает только если пользователь кликнул именно на этот элемент --><!-- Клики на дочерние элементы будут проигнорированы --><div class="modal-backdrop" on:click|self={closeModal}> <!-- Клик на контент не закроет модалку --> <div class="modal-content"> <p>Контент модалки</p> <button on:click={closeModal}>✕ Закрыть</button> </div></div>Цепочка модификаторов
Заголовок раздела «Цепочка модификаторов»Модификаторы можно комбинировать! Просто добавляй их через | один за другим:
<!-- preventDefault + stopPropagation вместе --><a href="#" on:click|preventDefault|stopPropagation={handleLink}> Ссылка без перехода и без всплытия</a>
<!-- once + preventDefault --><form on:submit|once|preventDefault={handleFirstSubmit}> <button type="submit">Отправить (один раз)</button></form>
<!-- capture + stopPropagation --><div on:click|capture|stopPropagation={handleCapture}> Поймаю и остановлю!</div>📦 Компонентные события: createEventDispatcher
Заголовок раздела «📦 Компонентные события: createEventDispatcher»Когда нужно передать событие из дочернего компонента в родительский, Svelte предоставляет createEventDispatcher. Это аналог $emit из Vue или пропсов-коллбэков в React:
<script> import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleClick() { // Отправляем событие 'notify' с данными dispatch('notify', { message: 'Кнопка нажата!', timestamp: new Date().toISOString() }) }
function handleError() { dispatch('error', { code: 404, message: 'Что-то пошло не так' }) }</script>
<button on:click={handleClick}>Нажми меня!</button><button on:click={handleError}>Симулировать ошибку</button>Функция dispatch принимает два аргумента:
- Имя события (строка) — любое имя, которое ты придумаешь
- Данные события (любой тип) — то, что будет доступно в
event.detail
👂 Подписка на компонентные события
Заголовок раздела «👂 Подписка на компонентные события»В родительском компоненте подписываемся точно так же, как на DOM-события — через on::
<script> import ChildButton from './ChildButton.svelte'
let lastMessage = '' let errorInfo = null
function handleNotify(event) { // Данные доступны в event.detail lastMessage = event.detail.message console.log('Время:', event.detail.timestamp) }
function handleError(event) { errorInfo = event.detail console.error('Ошибка:', event.detail.code, event.detail.message) }</script>
<ChildButton on:notify={handleNotify} on:error={handleError}/>
{#if lastMessage} <p>📩 Последнее сообщение: {lastMessage}</p>{/if}
{#if errorInfo} <p class="error">❌ Ошибка {errorInfo.code}: {errorInfo.message}</p>{/if}💡
event.detail— это стандартное свойство CustomEvent в браузере. Svelte использует нативный механизм кастомных событий!
🔀 Форвардинг событий
Заголовок раздела «🔀 Форвардинг событий»Иногда нужно “пробросить” событие через промежуточный компонент — например, если ты создаёшь компонент-обёртку <Button> над нативным <button>. Вместо того чтобы вручную создавать dispatcher и обработчик, можно использовать форвардинг:
<!-- FancyButton.svelte — просто добавь on:click без обработчика --><script> // Никакой логики не нужно!</script>
<button class="fancy-button" on:click on:focus on:blur> <slot /></button>
<!-- Стили... --><style> .fancy-button { background: #ff3e00; color: white; border: none; padding: 10px 24px; border-radius: 8px; cursor: pointer; font-weight: 600; }</style><!-- App.svelte — родитель использует FancyButton как обычный button --><script> import FancyButton from './FancyButton.svelte'
function handleClick(event) { console.log('Клик получен в App.svelte!', event) }</script>
<!-- on:click прозрачно форвардится от FancyButton.svelte --><FancyButton on:click={handleClick}> Нажми!</FancyButton>Ты можешь форвардить несколько событий одновременно: on:click on:mouseenter on:focus — каждое без обработчика. Это невероятно удобно для UI-библиотек!
Форвардинг компонентных событий
Заголовок раздела «Форвардинг компонентных событий»Форвардинг работает и для кастомных событий от createEventDispatcher:
<!-- MiddleLayer.svelte — пробрасывает событие 'message' дальше --><script> import DeepChild from './DeepChild.svelte'</script>
<!-- on:message без обработчика = форвардинг вверх по дереву --><DeepChild on:message />🔷 TypeScript: типизация кастомных событий
Заголовок раздела «🔷 TypeScript: типизация кастомных событий»В TypeScript можно строго типизировать кастомные события компонента с помощью ComponentEvents и дженериков createEventDispatcher:
<script lang="ts"> import { createEventDispatcher } from 'svelte'
// Описываем все события компонента с типами данных type Events = { notify: { message: string; timestamp: string } close: null submit: { formData: Record<string, string> } }
// Передаём тип в дженерик const dispatch = createEventDispatcher<Events>()
function handleSubmit() { // TypeScript проверяет: второй аргумент должен соответствовать типу dispatch('notify', { message: 'Форма отправлена!', timestamp: new Date().toISOString() }) }
function handleClose() { // Для событий без данных передаём null dispatch('close', null) }</script><!-- Parent.svelte с типизацией --><script lang="ts"> import MyForm from './MyForm.svelte' import type { ComponentEvents } from 'svelte'
// Получаем типы событий компонента type MyFormEvents = ComponentEvents<MyForm>
function handleNotify(event: MyFormEvents['notify']) { // TypeScript знает структуру event.detail! console.log(event.detail.message) // string ✅ console.log(event.detail.timestamp) // string ✅ }</script>
<MyForm on:notify={handleNotify} on:close={() => console.log('Закрыто')} />🆕 Svelte 5: Callback Props вместо createEventDispatcher
Заголовок раздела «🆕 Svelte 5: Callback Props вместо createEventDispatcher»В Svelte 5 появились руны (runes), и подход к событиям изменился! Теперь вместо createEventDispatcher рекомендуется использовать пропсы-коллбэки — как в React:
<!-- Button.svelte в Svelte 5 --><script> // Объявляем пропс-коллбэк через $props() let { onclick, children } = $props()</script>
<button {onclick}> {@render children()}</button><!-- Или с типизацией --><script lang="ts"> interface Props { onclick?: (event: MouseEvent) => void onnotify?: (data: { message: string }) => void children?: import('svelte').Snippet }
let { onclick, onnotify, children }: Props = $props()
function handleClick(event: MouseEvent) { onclick?.(event) onnotify?.({ message: 'Кнопка нажата!' }) }</script>
<button on:click={handleClick}> {@render children?.()}</button><!-- Использование в родителе — как обычный HTML! --><Button onclick={() => console.log('Клик!')} />⚠️ Совет Яши: Если ты только начинаешь изучать Svelte, освой сначала
createEventDispatcher(Svelte 4). В Svelte 5 они тоже работают, просто не рекомендуются. Callback props — это будущее!
⚖️ Сравнение: Svelte vs React vs Vue
Заголовок раздела «⚖️ Сравнение: Svelte vs React vs Vue»Посмотрим, как одно и то же реализуется в разных фреймворках — это помогает понять, в чём уникальность Svelte:
| Задача | Svelte | React | Vue |
|---|---|---|---|
| Клик | on:click={fn} | onClick={fn} | @click="fn" |
| Предотвратить переход | on:click|preventDefault | onClick={(e) => e.preventDefault()} | @click.prevent |
| Только один раз | on:click|once | Нужен useEffect + removeEventListener | @click.once |
| Остановить всплытие | on:click|stopPropagation | onClick={(e) => e.stopPropagation()} | @click.stop |
| Кастомные события | createEventDispatcher | Пропсы-коллбэки | $emit |
| Форвардинг | on:click без обработчика | Нужен spreад {...props} | v-on="$listeners" |
Ключевые отличия:
- Svelte — модификаторы встроены в синтаксис (
|once,|passive), очень декларативно - React — всё через JavaScript, нет специального синтаксиса для модификаторов
- Vue — модификаторы через точку (
@click.prevent), похоже на Svelte, но другой синтаксис
Svelte ближе всего к Vue в плане читаемости, но синтаксис on:событие напоминает нативный HTML-атрибут onclick, что делает код очень понятным!
🎮 Интерактивный Playground
Заголовок раздела «🎮 Интерактивный Playground»Поиграй с событиями прямо здесь! Переключайся между вкладками и нажимай на элементы чтобы увидеть как работают разные виды событий в реальном времени 👇