Kurs Svelte i SvelteKit · Moduł 2: Komponenty
Organizacja projektu
W tej lekcji4
Dobrze zorganizowany projekt to jak dobrze zorganizowana wioska wikingów - każdy wie gdzie co jest!
Zalecana struktura
src/
├── lib/ # Kod współdzielony (alias #lib)
│ ├── components/ # Komponenty UI
│ │ ├── ui/ # Podstawowe (Button, Input)
│ │ │ ├── Button.svelte
│ │ │ └── Input.svelte
│ │ ├── layout/ # Layouty (Header, Footer)
│ │ │ ├── Header.svelte
│ │ │ └── Footer.svelte
│ │ └── features/ # Funkcjonalne
│ │ ├── WarriorCard.svelte
│ │ └── ShipList.svelte
│ ├── state/ # Współdzielony stan (runy w plikach .svelte.js)
│ │ ├── warriors.svelte.js
│ │ └── ships.svelte.js
│ ├── utils/ # Funkcje pomocnicze
│ │ ├── formatters.js
│ │ └── validators.js
│ └── types/ # TypeScript types
│ └── index.ts
├── routes/ # Strony (SvelteKit)
│ ├── +page.svelte
│ ├── +layout.svelte
│ └── warriors/
│ └── +page.svelte
└── app.html # Szablon HTMLKatalog state/ trzyma stan współdzielony przez wiele komponentów. W Svelte 5 to zwykłe pliki .svelte.js, w których działają runy, np. $state. Starsze projekty mają tu katalog stores/ ze Svelte stores - poznasz je w Studni Mimira (moduł 8).
Aliasy importów
Projekt utworzony przez npx sv create ma w package.json alias #lib, który wskazuje katalog src/lib. To mechanizm Node.js o nazwie subpath imports (pole imports):
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}Dzięki niemu nie musisz liczyć ../ w ścieżkach. Pamiętaj tylko o rozszerzeniu pliku na końcu importu (.svelte, .js):
<script>
// Zamiast: import Button from '../../../lib/components/ui/Button.svelte'
import Button from '#lib/components/ui/Button.svelte';
// Współdzielony stan
import { army } from '#lib/state/warriors.svelte.js';
// Utils
import { formatDate } from '#lib/utils/formatters.js';
</script>W SvelteKit 2 ten sam alias nazywał się $lib i działał bez rozszerzeń - zobaczysz go w starszym kodzie. SvelteKit 3 już go nie tworzy.
Konwencje nazewnictwa
Pliki komponentów
- PascalCase:
WarriorCard.svelte,ShipList.svelte - Nazwa = co komponent reprezentuje
Pliki pomocnicze
- camelCase lub kebab-case:
formatters.js,api-client.ts
Katalogi
- kebab-case:
viking-camp/,ui-components/
Przykład: Komponent z pełną strukturą
Ten komponent zbiera wszystko w jednym miejscu. Rodzic przekazuje mu wojownika i funkcję onselect - komponent wywołuje ją, gdy ktoś wybierze kartę. To callback prop: w Svelte 5 komponenty komunikują się z rodzicem właśnie przez funkcje w props (więcej w Asgardzie, moduł 4). W projekcie TypeScript pliki .ts też importujesz z rozszerzeniem .js - TypeScript sam znajdzie plik index.ts.
<!-- src/lib/components/features/WarriorCard.svelte -->
<script lang="ts">
// Imports
import Avatar from '#lib/components/ui/Avatar.svelte';
import { formatHealth } from '#lib/utils/formatters.js';
import type { Warrior } from '#lib/types/index.js';
// Props - dane i callback od rodzica
interface Props {
warrior: Warrior;
selected?: boolean;
onselect?: (id: number) => void;
}
let { warrior, selected = false, onselect }: Props = $props();
// State
let showDetails = $state(false);
// Derived
let healthStatus = $derived(warrior.health > 50 ? 'healthy' : 'wounded');
</script>
<article
class="warrior-card"
class:selected
class:wounded={healthStatus === 'wounded'}
>
<Avatar src={warrior.avatar} alt={warrior.name} />
<h3>{warrior.name}</h3>
<p>Zdrowie: {formatHealth(warrior.health)}</p>
{#if showDetails}
<div class="details">
<p>Broń: {warrior.weapon}</p>
<p>Ranga: {warrior.rank}</p>
</div>
{/if}
<button onclick={() => onselect?.(warrior.id)}>Wybierz</button>
<button onclick={() => showDetails = !showDetails}>
{showDetails ? 'Ukryj' : 'Szczegóły'}
</button>
</article>
<style>
.warrior-card {
padding: 1rem;
border: 2px solid #444;
border-radius: 8px;
transition: all 0.2s;
}
.warrior-card:hover {
border-color: #d4af37;
}
.warrior-card.selected {
background: rgba(212, 175, 55, 0.1);
border-color: #d4af37;
}
.warrior-card.wounded {
border-color: #8b0000;
}
</style>interface Props opisuje, co komponent przyjmuje: wymaganego wojownika, opcjonalną flagę selected i opcjonalną funkcję onselect. Zapis onselect?.(warrior.id) wywoła funkcję tylko wtedy, gdy rodzic ją przekazał. W Svelte 4 zamiast callback props używano createEventDispatcher - w Svelte 5 to przestarzałe.
Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Gdzie w projekcie SvelteKit powinny znajdować się współdzielone komponenty UI?
Zadania praktyczne w grze
- Klikanie w kolejności
Kliknij konwencje nazewnictwa w kolejności: komponenty, pliki pomocnicze, katalogi