Kurs Vue.js · Moduł 6: Komponenty
Komponenty - Moduły Budowlane Bazy
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.vueKatalog 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. Co to jest komponent w Vue.js?
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.