16. Специальные элементы
🧿 Специальные элементы Svelte
Заголовок раздела «🧿 Специальные элементы Svelte»Svelte имеет набор встроенных «магических» элементов — они начинаются с svelte: и дают возможности, которые нельзя получить обычным HTML или компонентами. Это как суперспособности для шаблонов 🦸
Обзор всех специальных элементов
Заголовок раздела «Обзор всех специальных элементов»<svelte:self> — рекурсивный рендер компонента<svelte:component> — динамический выбор компонента<svelte:element> — динамический HTML тег<svelte:window> — события и привязки window<svelte:document> — события document<svelte:body> — события и классы body<svelte:head> — meta теги, title, CSS<svelte:fragment> — обёртка без DOM элемента<svelte:options> — настройки компонентаsvelte:self — Рекурсия 🔁
Заголовок раздела «svelte:self — Рекурсия 🔁»Позволяет компоненту рендерить самого себя. Идеально для деревьев:
<script lang="ts"> interface TreeNode { id: number label: string children?: TreeNode[] }
export let node: TreeNode export let depth = 0 let expanded = depth < 2 // Первые 2 уровня раскрыты</script>
<div class="tree-node" style="padding-left: {depth * 16}px"> <button class="toggle" on:click={() => expanded = !expanded} class:has-children={node.children?.length} > {#if node.children?.length} {expanded ? '▼' : '▶'} {:else} • {/if} {node.label} </button>
{#if expanded && node.children} {#each node.children as child (child.id)} <!-- Рекурсия! --> <svelte:self node={child} depth={depth + 1} /> {/each} {/if}</div><!-- Использование --><script> import TreeNode from './TreeNode.svelte'
const tree = { id: 1, label: '📁 Проект', children: [ { id: 2, label: '📁 src', children: [ { id: 3, label: '📄 App.svelte' }, { id: 4, label: '📄 main.ts' }, { id: 5, label: '📁 components', children: [ { id: 6, label: '📄 Button.svelte' }, ]}, ]}, { id: 7, label: '📄 package.json' }, ], }</script>
<TreeNode node={tree} />svelte:component — Динамические компоненты 🎭
Заголовок раздела «svelte:component — Динамические компоненты 🎭»Рендерит разные компоненты в зависимости от значения:
<script lang="ts"> import Button from './Button.svelte' import Link from './Link.svelte' import IconButton from './IconButton.svelte'
type ButtonType = 'button' | 'link' | 'icon'
export let type: ButtonType = 'button' export let label: string
const components = { button: Button, link: Link, icon: IconButton, } as const
$: component = components[type]</script>
<!-- Динамически выбирает компонент! --><svelte:component this={component} {label} {...$$restProps} /><!-- Паттерн: Page Router --><script> import HomePage from './pages/HomePage.svelte' import AboutPage from './pages/AboutPage.svelte' import ContactPage from './pages/ContactPage.svelte' import NotFoundPage from './pages/NotFoundPage.svelte'
const routes = { '/': HomePage, '/about': AboutPage, '/contact': ContactPage, }
let currentPath = window.location.pathname $: currentComponent = routes[currentPath] ?? NotFoundPage</script>
<!-- this={null} — ничего не рендерится --><svelte:component this={currentComponent} />Условие this={null}
Заголовок раздела «Условие this={null}»<script> let component = null // Ничего не рендерится!</script>
{#if condition} <!-- Альтернатива {#if} --> <svelte:component this={condition ? MyComponent : null} />{/if}svelte:element — Динамический HTML тег 🏷️
Заголовок раздела «svelte:element — Динамический HTML тег 🏷️»Когда нужен разный HTML тег, но одинаковое содержимое:
<script lang="ts"> export let level: 1 | 2 | 3 | 4 | 5 | 6 = 1 export let text: string
$: tag = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'</script>
<!-- Рендерит h1, h2, h3 и т.д. в зависимости от level --><svelte:element this={tag} class="heading"> {text}</svelte:element><!-- Паттерн: Полиморфный компонент --><script lang="ts"> export let as: string = 'div' // HTML тег export let href: string | undefined = undefined
// Если href есть — используем 'a' $: tag = href ? 'a' : as</script>
<svelte:element this={tag} {href} {...$$restProps}> <slot /></svelte:element><!-- Использование --><Polymorphic>Обычный div</Polymorphic><Polymorphic as="section">Секция</Polymorphic><Polymorphic href="/about">Ссылка (тег a)</Polymorphic><Polymorphic as="article">Статья</Polymorphic>svelte:window — Привязки к window 🪟
Заголовок раздела «svelte:window — Привязки к window 🪟»<script> let scrollY = 0 let innerWidth = 0 let innerHeight = 0 let online = navigator.onLine
// Клавиши function handleKeydown(event: KeyboardEvent) { if (event.key === 'Escape') closeModal() if (event.ctrlKey && event.key === 's') saveDocument() }</script>
<!-- Привязки к свойствам window --><svelte:window bind:scrollY bind:innerWidth bind:innerHeight bind:online on:keydown={handleKeydown} on:resize={() => console.log('Размер изменился')} on:beforeunload={e => { e.preventDefault(); return '' }}/>
<p>Прокрутка: {scrollY}px</p><p>Ширина окна: {innerWidth}px</p><p> {innerWidth < 768 ? '📱 Мобильный' : '🖥️ Десктоп'}</p>Все доступные привязки svelte:window
Заголовок раздела «Все доступные привязки svelte:window»bind:innerWidth — ширина viewportbind:innerHeight — высота viewportbind:outerWidth — ширина окна с рамкойbind:outerHeight — высота окна с рамкойbind:scrollX — горизонтальная прокруткаbind:scrollY — вертикальная прокруткаbind:online — онлайн статусsvelte:document — Документ
Заголовок раздела «svelte:document — Документ»<script> let pointerX = 0 let pointerY = 0 let activeElement: Element | null = null
function handlePointerMove(event: PointerEvent) { pointerX = event.clientX pointerY = event.clientY }</script>
<svelte:document on:pointermove={handlePointerMove} on:visibilitychange={() => { if (document.hidden) pauseVideo() }} bind:activeElement/>
<!-- Курсор всегда отображается --><div class="cursor" style="left: {pointerX}px; top: {pointerY}px"/>svelte:body — Управление body
Заголовок раздела «svelte:body — Управление body»<script> export let theme: 'dark' | 'light' = 'dark' export let modalOpen = false</script>
<svelte:body class:dark-theme={theme === 'dark'} class:modal-open={modalOpen} on:touchstart={handleTouchStart} on:touchend={handleTouchEnd}/>
<!-- При modalOpen=true на body добавляется класс modal-open --><!-- CSS: .modal-open { overflow: hidden; } -->svelte:head — Meta теги, Title 📰
Заголовок раздела «svelte:head — Meta теги, Title 📰»<!-- +page.svelte (SvelteKit) --><script> export let data</script>
<svelte:head> <!-- Динамический title --> <title>{data.post.title} | Мой блог</title>
<!-- Meta теги для SEO --> <meta name="description" content={data.post.excerpt} /> <meta property="og:title" content={data.post.title} /> <meta property="og:image" content={data.post.coverImage} /> <meta property="og:type" content="article" />
<!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={data.post.title} />
<!-- Canonical URL --> <link rel="canonical" href="https://myblog.com/posts/{data.post.slug}" />
<!-- Дополнительные стили для этой страницы --> <link rel="stylesheet" href="/syntax-highlight.css" /></svelte:head>
<article> <h1>{data.post.title}</h1> <!-- ... --></article><!-- Компонент для переиспользования SEO --><script lang="ts"> export let title: string export let description: string export let image: string | undefined = undefined export let noindex = false
const siteName = 'Мой Сайт' const siteUrl = 'https://mysite.com'</script>
<svelte:head> <title>{title} | {siteName}</title> <meta name="description" content={description} /> {#if noindex} <meta name="robots" content="noindex, nofollow" /> {/if} {#if image} <meta property="og:image" content={image} /> <meta name="twitter:image" content={image} /> {/if}</svelte:head>svelte:fragment — Группировка без обёртки
Заголовок раздела «svelte:fragment — Группировка без обёртки»<!-- Когда нужно передать несколько элементов в именованный слот --><Layout> <!-- БЕЗ svelte:fragment — нужен лишний div --> <div slot="header"> <Logo /> <Nav /> <SearchBar /> </div>
<!-- С svelte:fragment — нет лишнего div в DOM! --> <svelte:fragment slot="header"> <Logo /> <Nav /> <SearchBar /> </svelte:fragment></Layout>svelte:options — Настройки компонента ⚙️
Заголовок раздела «svelte:options — Настройки компонента ⚙️»<!-- В начале файла, до <script> --><svelte:options immutable={true} accessors={true} namespace="svg" customElement="my-button"/>
<script> export let count = 0</script>immutable — Оптимизация обновлений
Заголовок раздела «immutable — Оптимизация обновлений»<!-- svelte:options immutable=true говорит Svelte: "Данные не мутируются — только заменяются" --><svelte:options immutable={true} />
<script> export let items: string[] // Svelte теперь сравнивает items по ссылке (===) // вместо глубокого сравнения // БЫСТРЕЕ для больших списков!</script>
{#each items as item} <li>{item}</li>{/each}accessors — Внешний доступ к экспортам
Заголовок раздела «accessors — Внешний доступ к экспортам»<svelte:options accessors={true} />
<script> export let count = 0 export function increment() { count++ }</script><!-- Родитель может обращаться напрямую! --><script> import Counter from './Counter.svelte'
let counter: Counter
function external() { console.log(counter.count) // Читаем counter.increment() // Вызываем метод }</script>
<Counter bind:this={counter} /><button on:click={external}>Внешнее управление</button>customElement — Web Components
Заголовок раздела «customElement — Web Components»<!-- Компилируется в настоящий Web Component! --><svelte:options customElement="my-cool-button" />
<script> export let label = 'Нажми' export let variant = 'primary'</script>
<button class="btn btn--{variant}"> {label}</button>
<!-- Теперь можно использовать без Svelte! --><!-- <my-cool-button label="Привет" variant="secondary"></my-cool-button> -->