5. Props
Props в Svelte: export let и передача данных 📦
Заголовок раздела «Props в Svelte: export let и передача данных 📦»Привет! 👋 Яша снова здесь. Мы уже знаем, как работает реактивность внутри одного компонента. Теперь пора научиться передавать данные между компонентами. В Svelte это делается через export let — и это один из тех моментов, когда ты скажешь “Ого, как просто!” 😄
🤔 Что такое props вообще?
Заголовок раздела «🤔 Что такое props вообще?»Props (сокращение от properties) — это способ передать данные сверху вниз: от родительского компонента к дочернему. Это главный механизм переиспользования компонентов.
Представь, что у тебя есть компонент UserCard. Он должен показывать разных пользователей с разными именами, аватарками, биографиями. Вместо того, чтобы создавать UserCard1.svelte, UserCard2.svelte, UserCard3.svelte — ты создаёшь один компонент и передаёшь данные как props.
В React:
function UserCard({ name, age, online }) { return <div>{name} — {age} лет</div>;}
<UserCard name="Яша" age={25} online={true} />В Vue:
<script setup>const props = defineProps<{ name: string; age: number; online: boolean }>();</script>В Svelte — элегантнее всех:
<script> export let name; export let age; export let online = false; // дефолтное значение</script>
<div>{name} — {age} лет</div>📤 export let — магия компилятора
Заголовок раздела «📤 export let — магия компилятора»Ключевое слово export в контексте Svelte-компонента работает не как обычный ES6 export. Svelte перехватывает его и превращает в объявление пропса.
<script> export let name = 'Мир'; // prop с дефолтным значением export let emoji = '👋'; // ещё один prop
// Это обычная переменная — НЕ prop let greeting = 'Привет';</script>
<p>{greeting}, {name}! {emoji}</p><!-- App.svelte — родительский компонент --><script> import Greeting from './Greeting.svelte';</script>
<Greeting name="Яша" emoji="🚀" /><!-- Выведет: "Привет, Яша! 🚀" -->
<Greeting name="Мир" /><!-- Выведет: "Привет, Мир! 👋" (emoji — дефолтное) -->
<Greeting /><!-- Выведет: "Привет, Мир! 👋" (оба дефолтных) -->💡 Почему именно
export? Svelte умно переиспользует синтаксис ES6. С точки зрения компилятора: то, что компонент “экспортирует” — это то, что он принимает снаружи. Гениально и лаконично!
✅ Обязательные vs необязательные props
Заголовок раздела «✅ Обязательные vs необязательные props»В Svelte нет встроенной системы валидации пропсов (как PropTypes в React), но есть чёткое правило:
<script> // ⚠️ Без дефолтного значения — prop "обязательный" // JS не выбросит ошибку, но TypeScript предупредит export let userId; // обязательный по духу export let name; // обязательный по духу
// ✅ С дефолтным значением — необязательный export let online = false; export let theme = 'light'; export let count = 0; export let items = [];</script>Если передать компоненту незнакомый prop, Svelte выбросит предупреждение в dev-режиме:
<Unknown prop 'unknownProp' received by 'MyComponent'>Это помогает отлавливать опечатки! 🎯
📘 TypeScript: типизированные props
Заголовок раздела «📘 TypeScript: типизированные props»С TypeScript работа с пропсами становится ещё удобнее:
<script lang="ts"> // Базовые типы export let name: string; export let age: number = 0; export let online: boolean = false; export let tags: string[] = [];
// Объект export let user: { id: number; name: string; avatar: string };
// Enum через union export let theme: 'light' | 'dark' | 'auto' = 'auto'; export let size: 'sm' | 'md' | 'lg' = 'md';
// Опциональный prop через undefined export let description: string | undefined = undefined;
// Функция как prop (callback) export let onClick: (event: MouseEvent) => void = () => {}; export let onValueChange: (value: number) => void = () => {};</script>Через интерфейс:
<script lang="ts"> interface Props { name: string; age: number; online?: boolean; // ? — опциональный (есть дефолт) theme?: 'light' | 'dark'; onClose?: () => void; }
// В Svelte 4 нельзя деструктурировать из интерфейса напрямую, // поэтому объявляем каждый prop отдельно: export let name: Props['name']; export let age: Props['age']; export let online: Props['online'] = false; export let theme: Props['theme'] = 'light'; export let onClose: Props['onClose'] = undefined;</script>В Svelte 5 с рунами всё ещё проще:
<script lang="ts"> interface Props { name: string; age: number; online?: boolean; }
// Svelte 5: деструктурируем из $props()! const { name, age, online = false }: Props = $props();</script>🚀 Передача props: все способы
Заголовок раздела «🚀 Передача props: все способы»Обычная передача
Заголовок раздела «Обычная передача»<UserCard name="Яша" age={25} online={true} theme="dark" />Динамические значения
Заголовок раздела «Динамические значения»<script> let currentUser = { name: 'Яша', age: 25 }; let isOnline = true;</script>
<UserCard name={currentUser.name} age={currentUser.age} online={isOnline}/>Числа, булевы, объекты — через {}
Заголовок раздела «Числа, булевы, объекты — через {}»<!-- ✅ Число --><Progress value={75} max={100} />
<!-- ✅ Булево: просто написать имя = true --><Button disabled /><!-- Это то же самое, что: --><Button disabled={true} />
<!-- ✅ Объект --><Chart data={{ labels: ['янв', 'фев'], values: [10, 20] }} />
<!-- ✅ Функция-колбэк --><Button onClick={() => console.log('click!')} />⚡ Сокращённый синтаксис (shorthand)
Заголовок раздела «⚡ Сокращённый синтаксис (shorthand)»Если имя переменной совпадает с именем пропса — можно использовать сокращение:
<script> let name = 'Яша'; let age = 25; let online = true;</script>
<!-- Обычный способ: --><UserCard name={name} age={age} online={online} />
<!-- Сокращённый способ (shorthand): --><UserCard {name} {age} {online} />
<!-- Работает только когда переменная называется так же, как prop! -->Это мелочь, но когда передаёшь много пропсов — сильно сокращает код. В React такого нет (там нужен spread: <UserCard {...{name, age, online}} />).
🌊 Spread props: передать объект как пропсы
Заголовок раздела «🌊 Spread props: передать объект как пропсы»Если у тебя есть объект с данными — можно передать его целиком:
<script> let userProps = { name: 'Яша', age: 25, online: true, theme: 'dark', };</script>
<!-- Обычная передача: --><UserCard name={userProps.name} age={userProps.age} online={userProps.online} theme={userProps.theme} />
<!-- Spread: короче и чище! --><UserCard {...userProps} />Можно комбинировать spread с явными пропсами:
<!-- Spread + отдельные пропсы --><UserCard {...userProps} theme="light" /><!-- theme="light" перезапишет userProps.theme -->Это особенно удобно при переадресации пропсов между компонентами:
<!-- Wrapper.svelte — просто передаёт всё в InnerComponent --><script> import InnerComponent from './InnerComponent.svelte'; // получаем все свои props export let foo; export let bar;</script>
<InnerComponent {foo} {bar} /><!-- или если много пропсов — через $$props: --><InnerComponent {...$$props} />🔮 $$props: все пропсы как объект
Заголовок раздела «🔮 $$props: все пропсы как объект»$$props — это специальная переменная Svelte, которая содержит все переданные пропсы в виде объекта, включая те, которые ты не объявил через export let:
<script> export let name = ''; export let age = 0;
// $$props содержит { name: 'Яша', age: 25, ...и любые другие } console.log($$props); // { name: 'Яша', age: 25, class: 'user-card', id: 'main' }</script>
<!-- Можно передать все props куда-то ещё --><div {...$$props}> {name} — {age}</div>⚠️ Осторожно! Если ты передаёшь
$$propsв DOM-элемент (<div>,<button>и т.д.), это может передать невалидные HTML-атрибуты в DOM. Лучше использовать$$restProps.
🎭 $$restProps: пропсы без явно объявленных
Заголовок раздела «🎭 $$restProps: пропсы без явно объявленных»$$restProps — это $$props минус все явно объявленные через export let:
<script> export let variant = 'primary'; export let size = 'md';
// $$restProps содержит всё остальное: // onClick, disabled, type, class, id, aria-* и т.д. // Всё что угодно, кроме variant и size</script>
<button class={'btn btn-' + variant + ' btn-' + size} {...$$restProps}> <slot /></button>Использование:
<Button variant="danger" size="lg" onClick={handleClick} disabled={isLoading} aria-label="Удалить"> Удалить</Button>
<!-- В итоге в DOM: <button class="btn btn-danger btn-lg" onclick="..." disabled aria-label="Удалить" > Удалить </button>-->$$restProps — незаменимый инструмент для создания UI-библиотек и враппер-компонентов над нативными HTML-элементами. Ты контролируешь свои пропсы, а все остальные атрибуты проходят насквозь.
🔗 Двусторонняя привязка через bind:
Заголовок раздела «🔗 Двусторонняя привязка через bind:»По умолчанию props — это односторонняя связь: от родителя к дочернему. Но иногда нужна двусторонняя привязка:
<script> export let value = 0;</script>
<input type="number" bind:value /><!-- value изменяется через пользовательский ввод --><script> import NumberInput from './NumberInput.svelte'; let count = 10;</script>
<!-- bind: синхронизирует count с value внутри NumberInput --><NumberInput bind:value={count} /><p>Текущее значение: {count}</p><!-- count обновится при изменении input! -->Сокращение (если имена совпадают):
<NumberInput bind:value /><!-- то же самое, что bind:value={value} -->💡
bind:— это синтаксический сахар для паттерна “prop + event”. Под капотом Svelte генерирует обработчик события и обновление родительской переменной автоматически.
🔧 Динамические компоненты
Заголовок раздела «🔧 Динамические компоненты»Иногда нужно рендерить компонент динамически — когда сам компонент определяется в runtime:
<script> import Icon from './Icon.svelte'; import Badge from './Badge.svelte'; import Label from './Label.svelte';
let selectedComponent = Icon; let props = { size: 24, color: 'red' };</script>
<!-- svelte:component — рендерит компонент динамически --><svelte:component this={selectedComponent} {...props} />
<!-- Меняем компонент и пропсы: --><button on:click={() => { selectedComponent = Badge; props = { text: 'Новый' }; }}> Переключить на Badge</button>Когда this={null} или this={undefined} — ничего не рендерится. Удобно для условного рендера!
📋 Readonly props через export const
Заголовок раздела «📋 Readonly props через export const»Иногда компонент хочет “опубликовать” что-то вовне, но не принимать это как prop:
<script> // НЕ prop — это просто экспорт из модуля компонента export const reset = () => { elapsed = 0; };
let elapsed = 0; // ...таймер</script>
<p>Прошло: {elapsed} сек</p><script> import Timer from './Timer.svelte'; let timerRef;</script>
<Timer bind:this={timerRef} /><button on:click={() => timerRef.reset()}>Сбросить</button>export const и export function создают публичное API компонента — методы и значения, доступные родителю через bind:this.
🛡️ Паттерны валидации props
Заголовок раздела «🛡️ Паттерны валидации props»Svelte не имеет встроенного механизма валидации (как PropTypes в React или runtime validators в Vue). Но есть паттерны:
<script> export let rating = 0; export let size = 'md';
// Паттерн 1: onMount-валидация (только dev) import { onMount } from 'svelte'; onMount(() => { if (rating < 0 || rating > 5) { console.warn('Rating должен быть от 0 до 5, получено:', rating); } if (!['sm', 'md', 'lg'].includes(size)) { console.warn('Неверный size:', size, '— используем md'); size = 'md'; } });
// Паттерн 2: реактивная нормализация $: normalizedRating = Math.max(0, Math.min(5, rating));
// Паттерн 3: TypeScript (лучший подход!) // export let size: 'sm' | 'md' | 'lg' = 'md'; // TypeScript не даст передать неверное значение на этапе компиляции</script>Рекомендация Яши: используй TypeScript — это и есть лучшая валидация. Ошибки находятся в IDE, а не в runtime.
🎯 Типичные ошибки и как их избежать
Заголовок раздела «🎯 Типичные ошибки и как их избежать»1. Попытаться мутировать prop напрямую:
<script> export let count = 0;
function increment() { count++; // ⚠️ Работает, но антипаттерн! // Svelte предупредит в dev-режиме // Родитель не узнает об изменении (если нет bind:) }</script>Лучше: использовать bind: для двусторонней привязки или emit событие.
2. Передать объект без распаковки:
<script> let user = { name: 'Яша', age: 25 };</script>
<!-- ❌ Передаёт один prop "user" с объектом --><UserCard user={user} />
<!-- ✅ Spread — передаёт name и age как отдельные props --><UserCard {...user} />3. Использовать $$props в DOM-элементах без осторожности:
<!-- ⚠️ Может добавить невалидные атрибуты в DOM --><div {...$$props}>{content}</div>
<!-- ✅ Используй $$restProps — только нераспознанные props --><div {...$$restProps}>{content}</div>🎮 Интерактивный Playground
Заголовок раздела «🎮 Интерактивный Playground»Настраивай пропсы через панель управления и наблюдай, как дочерний компонент обновляется в реальном времени!