Kurs Vue.js · Moduł 6: Komponenty

Komponenty - Moduły Budowlane Bazy

4 min czytania
W tej lekcji5

Witaj w Drukarni 3D NOVA LAB! Dr. Nova mówi: tak jak drukarka 3D tworzy modułowe części habitatu marsjańskiego, Ty nauczysz się tworzyć komponenty Vue - wielokrotnego użytku elementy aplikacji.

Do tej pory cały panel mieszkał w jednym pliku App.vue. To wystarcza na jeden ekran, ale baza rośnie: pięć identycznych kart modułów to pięć kopii tego samego HTML-a, a poprawka w jednej karcie wymaga poprawienia wszystkich pięciu. Komponent rozwiązuje ten problem - projektujesz element raz i używasz go dowolnie wiele razy.

Czego się nauczysz

  • tworzyć komponenty i nazywać je zgodnie z konwencją,
  • importować je lokalnie i rejestrować globalnie,
  • składać interfejs z drzewa komponentów,
  • uruchamiać kod w kolejnych fazach życia komponentu,
  • izolować style i wydzielać wspólną logikę do composables.

Czym jest Komponent?

Komponent to wielokrotnego użytku, samodzielna jednostka UI z własnym szablonem, logiką i stylami - jak moduł habitatowy, który można łączyć z innymi. Oto karta modułu zapisana w pliku ModuleCard.vue, najpierw szablon i dane:

1<!-- ModuleCard.vue -->
2<template>
3  <article class="module-card" style="background: #0a0e27; border: 1px solid #00b4d8; color: #00ff88;">
4    <div class="module-icon">◆</div>
5    <h3>{{ moduleName }}</h3>
6    <p>{{ moduleType }}</p>
7    <p class="status">{{ status }}</p>
8  </article>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const moduleName = ref('Oxygen Generator Alpha')
15const moduleType = ref('Life Support')
16const status = ref('ACTIVE')
17</script>

Szablon pokazuje nazwę, typ i status modułu, a trzy kontenery ref() przechowują te dane - to ta sama składnia, której używałeś w Strefie Startowej. Nowe jest tylko to, że plik ma własną nazwę i zostanie użyty w innym miejscu. Obudowę karty zapisujemy w tym samym pliku, w sekcji ze stylami ograniczonymi do komponentu:

1<style scoped>
2.module-card {
3  border-radius: 8px;
4  padding: 1rem;
5  max-width: 300px;
6}
7
8.module-icon {
9  font-size: 3rem;
10  text-align: center;
11}
12
13.status {
14  color: #00ff88;
15  font-weight: bold;
16}
17</style>

Sekcje ustawiamy w kolejności stosowanej w NOVA LAB od Strefy Startowej: <template>, <script setup>, <style scoped>. W edytorze zobaczysz też pliki z <script setup> na górze - to również poprawne, bo Vue wymaga tylko spójności w projekcie i stylu na końcu.

Używanie Komponentu

Żeby wydrukować kartę w bazie, importujemy plik w App.vue i używamy nazwy importu jak znacznika HTML:

1<!-- App.vue -->
2<template>
3  <div class="production-bay" style="background: #0a0e27;">
4    <ModuleCard />
5    <ModuleCard />
6    <ModuleCard />
7  </div>
8</template>
9
10<script setup>
11import ModuleCard from './components/ModuleCard.vue'
12</script>

Każdy znacznik <ModuleCard /> tworzy osobną instancję komponentu z własnymi danymi, więc zmiana statusu w jednej karcie nie dotknie pozostałych. Plik ModuleCard.vue się nie zmienił i nie zmieni się bez względu na liczbę kopii. Na razie wszystkie trzy karty pokazują te same dane - przekazywanie każdej karcie innych wartości, czyli props, poznasz w Stacji Komunikacji. To właśnie główna zaleta architektury komponentowej: kod piszesz raz, używasz wielokrotnie, a poprawkę wprowadzasz w jednym miejscu.

Nazewnictwo Komponentów

Nazwy komponentów piszemy w PascalCase, czyli każde słowo wielką literą, bez spacji i podkreśleń. Tak samo nazywamy pliki: ModuleCard.vue, a nie module_card.vue czy moduleCard.vue:

1<script setup>
2// PascalCase - zalecane
3import ArtworkCard from './ArtworkCard.vue'
4import UserProfile from './UserProfile.vue'
5import NavigationMenu from './NavigationMenu.vue'
6
7// W template - PascalCase lub kebab-case
8</script>
9
10<template>
11  <!-- PascalCase -->
12  <ArtworkCard />
13  <UserProfile />
14
15  <!-- lub kebab-case -->
16  <artwork-card />
17  <user-profile />
18</template>

W szablonach plików .vue działają obie formy znacznika, ale polecam PascalCase - od razu widać, że <UserProfile /> to komponent, a <div> to zwykły HTML. Druga zasada z oficjalnego przewodnika stylu mówi, że nazwa komponentu powinna mieć co najmniej dwa słowa. Wszystkie elementy HTML mają nazwy jednowyrazowe, więc komponent Card mógłby zderzyć się z przyszłym znacznikiem przeglądarki, a ModuleCard nie zderzy się nigdy. Wyjątkiem jest komponent główny App, a komponenty bazowe dostają przedrostek, na przykład BaseButton.

Organizacja Plików

Gdy komponentów przybywa, grupujemy je w podkatalogach src/components/ według roli, jaką pełnią w bazie:

1src/
2├── components/
3│   ├── common/
4│   │   ├── BaseButton.vue
5│   │   ├── BasePanelCard.vue
6│   │   └── BaseInput.vue
7│   ├── modules/
8│   │   ├── ModuleCard.vue
9│   │   ├── ModuleList.vue
10│   │   └── ModuleDetail.vue
11│   └── layout/
12│       ├── MissionHeader.vue
13│       ├── ControlFooter.vue
14│       └── NavSidebar.vue

Katalog common/ trzyma komponenty bazowe, modules/ karty i listy modułów, a layout/ elementy ramy ekranu. Komponenty wielokrotnego użytku zawsze trafiają do src/components/, a całe ekrany podpinane pod nawigację do znanego ze struktury projektu katalogu src/views/.

W edytorze poniżej App.vue używa komponentu PrinterStatusPanel trzy razy. Zobaczysz w nim defineProps i defineEmits - to kanały komunikacji ze Stacji Komunikacji, a dziś wystarczy, że panel jest osobnym plikiem zaimportowanym i użytym jak znacznik.

Zapamiętaj: komponent to wydrukowany moduł habitatu - projektujesz go raz, w pliku o wielowyrazowej nazwie w PascalCase, i montujesz w bazie tyle razy, ile potrzebujesz.

Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3import PrinterStatusPanel from './PrinterStatusPanel.vue'
4
5const printers = ref([
6  {
7    id: 'p1',
8    name: '3D Printer Alpha',
9    status: 'printing',
10    progress: 67,
11    material: 'PLA Composite',
12    temperature: 210
13  },
14  {
15    id: 'p2',
16    name: '3D Printer Beta',
17    status: 'idle',
18    progress: 0,
19    material: 'Carbon Fiber',
20    temperature: 25
21  },
22  {
23    id: 'p3',
24    name: '3D Printer Gamma',
25    status: 'error',
26    progress: 45,
27    material: 'Titanium Alloy',
28    temperature: 185
29  }
30])
31
32function handlePrinterAction(printerId, action) {
33  const printer = printers.value.find(p => p.id === printerId)
34  if (printer) {
35    console.log(`Action ${action} on ${printer.name}`)
36    if (action === 'start') {
37      printer.status = 'printing'
38    } else if (action === 'stop') {
39      printer.status = 'idle'
40      printer.progress = 0
41    } else if (action === 'pause') {
42      printer.status = 'paused'
43    }
44  }
45}
46</script>
47
48<template>
49  <div class="printing-lab">
50    <div class="header">
51      <h1>Drukarnia 3D - NOVA LAB</h1>
52      <p class="subtitle">Panel kontrolny drukarek 3D na Marsie</p>
53    </div>
54
55    <div class="info-panel">
56      <h3>Definiowanie komponentów z props i emits</h3>
57      <p>Komponenty przyjmują dane przez <strong>props</strong> i komunikują się przez <strong>emits</strong></p>
58    </div>
59
60    <div class="printers-grid">
61      <PrinterStatusPanel
62        v-for="printer in printers"
63        :key="printer.id"
64        :printer-id="printer.id"
65        :printer-name="printer.name"
66        :status="printer.status"
67        :progress="printer.progress"
68        :material="printer.material"
69        :temperature="printer.temperature"
70        @action="handlePrinterAction"
71      />
72    </div>
73  </div>
74</template>
75
76<style scoped>
77.printing-lab {
78  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
79  min-height: 100vh;
80  padding: 2rem;
81  color: #ffffff;
82  font-family: 'Courier New', monospace;
83}
84
85.header {
86  text-align: center;
87  margin-bottom: 2rem;
88  padding-bottom: 1rem;
89  border-bottom: 2px solid #00ff88;
90}
91
92.header h1 {
93  font-size: 2.5rem;
94  color: #00ff88;
95  text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
96  margin: 0;
97}
98
99.subtitle {
100  color: #00b4d8;
101  font-size: 1.2rem;
102  margin-top: 0.5rem;
103}
104
105.info-panel {
106  background: rgba(0, 180, 216, 0.1);
107  border: 2px solid #00b4d8;
108  border-radius: 10px;
109  padding: 1.5rem;
110  margin-bottom: 2rem;
111}
112
113.info-panel h3 {
114  color: #00b4d8;
115  margin-top: 0;
116}
117
118.info-panel strong {
119  color: #00ff88;
120}
121
122.printers-grid {
123  display: grid;
124  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
125  gap: 1.5rem;
126}
127</style>

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. Co to jest komponent w Vue.js?

  2. 2. Jaka jest główna zaleta komponentowej architektury w Vue?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Utwórz komponent MarsModule.vue z sekcjami template, script setup i style scoped.

  • Klikanie w kolejności

    Ułóż sekcje Single File Component w kolejności stosowanej w NOVA LAB:

  • Edytor kodu

    Stwórz komponent StatusPanel.vue z ref() dla statusu i temperatury wyświetlanymi w template.

Przydatne artykuły