Kurs Svelte i SvelteKit · Moduł 2: Komponenty

Organizacja projektu

3 min czytania ·
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 HTML

Katalog 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. 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

Przydatne artykuły