Kurs Vue.js · Moduł 2: Składnia szablonów
Template Refs
W tej lekcji5
Czasem potrzebujesz bezpośredniego dostępu do elementu DOM - jak inżynier, który musi sprawdzić fizyczny sensor.
Szablony opisują, jak panel ma wyglądać, a Vue sam dba o elementy strony. Są jednak zadania, których deklaratywnie nie zapiszesz: ustawić kursor w polu komendy zaraz po otwarciu panelu, zmierzyć kontener albo przekazać element <canvas> zewnętrznej bibliotece wykresów. Wtedy potrzebujesz uchwytu do prawdziwego elementu - i to dają template refs. W drugiej części lekcji poznasz też kilka dyrektyw specjalnych do zadań nietypowych.
Podstawowe Template Refs
Specjalny atrybut ref oznacza element w szablonie. W skrypcie deklarujesz ref o tej samej nazwie z wartością null, a po zamontowaniu komponentu Vue wkłada do niego element. Moment zamontowania łapie funkcja onMounted - hak cyklu życia, który Vue wywołuje, gdy komponent trafił już na stronę; cykl życia omówimy w module 6.
1<template>
2 <div class="input-panel" style="background: #0a0e27; padding: 1rem;">
3 <input ref="inputRef" type="text" placeholder="Kod modułu"
4 style="background: #0a0e27; border: 1px solid #00b4d8; color: #00ff88; padding: 0.5rem;">
5 <button @click="focusInput" class="btn-focus">Ustaw fokus</button>
6 </div>
7</template>
8
9<script setup>
10import { ref, onMounted } from 'vue'
11
12// Nazwa musi odpowiadać atrybutowi ref w template
13const inputRef = ref(null)
14
15function focusInput() {
16 inputRef.value?.focus()
17}
18
19onMounted(() => {
20 // Element jest dostępny po zamontowaniu
21 inputRef.value?.focus()
22})
23</script>Kolejność jest zawsze taka sama: deklaracja ref(null), atrybut ref="inputRef" na elemencie, zamontowanie i dopiero wtedy użycie. Przed montażem, a także po usunięciu elementu przez v-if, ref zawiera null, dlatego ?. chroni przed błędem. To ta sama funkcja ref() co przy danych - kontener trzyma tu po prostu element zamiast liczby.
Od Vue 3.5 zalecany jest pomocnik useTemplateRef. Jego argument musi odpowiadać wartości atrybutu ref, a zmienna może nazywać się dowolnie:
1<script setup>
2import { useTemplateRef, onMounted } from 'vue'
3
4const commandInput = useTemplateRef('command-input')
5
6onMounted(() => {
7 commandInput.value?.focus()
8})
9</script>
10
11<template>
12 <input ref="command-input" placeholder="Kod modułu">
13</template>Działa identycznie, a w TypeScripcie typ elementu zostanie wywnioskowany sam. Starszy zapis nadal działa i spotkasz go w istniejącym kodzie oraz w zadaniach, ale w nowych projektach polecam useTemplateRef.
Refs w v-for
Gdy ref stoi na elemencie z v-for, po zamontowaniu dostajesz tablicę elementów, a nie pojedynczy element:
1<template>
2 <div class="modules-list">
3 <ul style="list-style: none; background: #0a0e27;">
4 <li v-for="module in modules" :key="module.id" ref="itemRefs"
5 style="color: #00b4d8; padding: 0.5rem; border-left: 2px solid #00ff88;">
6 {{ module.name }}
7 </li>
8 </ul>
9 </div>
10</template>
11
12<script setup>
13import { ref, onMounted } from 'vue'
14
15const modules = ref([
16 { id: 1, name: 'Oxygen Generator' },
17 { id: 2, name: 'Water Recycler' }
18])
19
20const itemRefs = ref([])
21
22onMounted(() => {
23 console.log(itemRefs.value) // Tablica elementów LI
24})
25</script>Konsola pokaże dwa elementy li. Dokumentacja zastrzega jednak, że kolejność tej tablicy nie musi odpowiadać kolejności danych, więc nie zakładaj, że element o indeksie 0 to pierwszy moduł.
Function Refs
Zamiast nazwy możesz podać funkcję, używając dynamicznego wiązania :ref. Vue wywoła ją z elementem przy każdej aktualizacji komponentu, a przy odmontowaniu z null:
1<template>
2 <input :ref="setInputRef" type="text"
3 style="background: #0a0e27; border: 1px solid #00ff88; color: #00b4d8;">
4</template>
5
6<script setup>
7import { ref } from 'vue'
8
9const dynamicRef = ref(null)
10
11function setInputRef(el) {
12 dynamicRef.value = el
13 console.log('Element ustawiony:', el)
14}
15</script>Sam decydujesz, gdzie zapisać element, na przykład w mapie według identyfikatora. Pamiętaj tylko o przypadku null.
Refs na Komponentach
Atrybut ref na komponencie daje dostęp do jego instancji. SensorPanel.vue to osobny plik komponentu, importowany jak zwykły moduł - komponentami zajmiemy się w module 6. Rodzic wywołuje metodę dziecka po zamontowaniu:
1<template>
2 <SensorPanel ref="sensorRef" />
3</template>
4
5<script setup>
6import { ref, onMounted } from 'vue'
7import SensorPanel from './SensorPanel.vue'
8
9const sensorRef = ref(null)
10
11onMounted(() => {
12 // Dostęp do metod i właściwości komponentu
13 sensorRef.value?.refreshData()
14})
15</script>Komponenty z <script setup> są domyślnie prywatne: rodzic nie zobaczy niczego, dopóki dziecko samo nie udostępni tego makrem defineExpose, którego nie trzeba importować:
1<!-- SensorPanel.vue - musi eksponować metody -->
2<script setup>
3import { ref } from 'vue'
4
5const sensorData = ref({})
6
7function refreshData() {
8 console.log('Odświeżanie danych z sensorów!')
9}
10
11// defineExpose - udostępnij rodzicowi
12defineExpose({
13 refreshData,
14 sensorData
15})
16</script>defineExpose trzeba wywołać przed pierwszym await. Takie połączenie wiąże komponenty ciasno, więc używaj go oszczędnie - zwykle lepsze są propsy i zdarzenia.
Dyrektywy Specjalne
Kilka wbudowanych dyrektyw rozwiązuje zadania rzadsze, ale warto je znać. v-text ustawia textContent elementu, więc zastępuje całą jego zawartość, a wąsy podmieniają tylko swój fragment:
1<template>
2 <p v-text="statusMessage"></p>
3 <p>Status: {{ statusMessage }}</p>
4</template>Oba akapity pokażą komunikat, ale tylko drugi zachowa napis Status. v-once renderuje element raz i potem traktuje go jak statyczny. v-memo (Vue 3.2+) zapamiętuje fragment szablonu i aktualizuje go tylko wtedy, gdy zmieni się któraś wartość z tablicy:
1<template>
2 <p v-once>Kod misji: {{ missionCode }}</p>
3
4 <div v-for="module in modules" :key="module.id" v-memo="[module.id === selectedId]">
5 {{ module.name }} - wybrany: {{ module.id === selectedId }}
6 </div>
7</template>Późniejsza zmiana missionCode nie odświeży akapitu. Po zmianie selectedId przerysują się tylko dwa moduły, którym zmieniła się flaga. v-memo="[]" działa jak v-once, a źle dobrane zależności zostawią na ekranie nieaktualne dane, dlatego to narzędzie do bardzo długich list. Dwie ostatnie dyrektywy dotyczą kompilacji:
1<template>
2 <code v-pre>{{ to nie zostanie skompilowane }}</code>
3</template>
4
5<style>
6[v-cloak] {
7 display: none;
8}
9</style>v-pre pomija kompilację elementu i jego dzieci, więc wąsy zobaczysz dosłownie. v-cloak jest potrzebny tylko bez kroku budowania, gdy Vue kompiluje szablon zapisany wprost w HTML: razem z tą regułą CSS ukrywa surowe {{ }} do chwili zamontowania aplikacji. W projekcie Vite nie jest potrzebny.
Traktuj template refs jako ostateczność - najpierw sprawdź, czy wystarczy szablon. W projekcie modułu połączysz wszystkie te narzędzia w jeden panel hologramów.
Zapamiętaj: template ref to klucz serwisowy do jednego urządzenia - sięgasz po niego dopiero po montażu i tylko wtedy, gdy panel sterowania nie wystarcza.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref, onMounted } from 'vue'
3
4// Template refs (uchwyty do elementów)
5const inputRef = ref(null)
6const containerRef = ref(null)
7const canvasRef = ref(null)
8
9// Dane
10const focusCount = ref(0)
11const containerInfo = ref({})
12const canvasDrawn = ref(false)
13
14// Dane reaktywne do pokazu v-once
15const staticData = ref({
16 launchDate: '2087-03-15',
17 missionCode: 'NOVA-MARS-001',
18 initialCrew: 6
19})
20
21const dynamicCounter = ref(0)
22
23// Funkcje
24function focusInput() {
25 if (inputRef.value) {
26 inputRef.value.focus()
27 focusCount.value++
28 }
29}
30
31function getContainerDimensions() {
32 if (containerRef.value) {
33 const rect = containerRef.value.getBoundingClientRect()
34 containerInfo.value = {
35 width: Math.round(rect.width),
36 height: Math.round(rect.height),
37 top: Math.round(rect.top),
38 left: Math.round(rect.left)
39 }
40 }
41}
42
43function drawOnCanvas() {
44 if (canvasRef.value) {
45 const ctx = canvasRef.value.getContext('2d')
46 const canvas = canvasRef.value
47
48 // Wyczyść canvas
49 ctx.fillStyle = '#0a0e27'
50 ctx.fillRect(0, 0, canvas.width, canvas.height)
51
52 // Narysuj Marsa
53 ctx.beginPath()
54 ctx.arc(200, 150, 80, 0, Math.PI * 2)
55 ctx.fillStyle = '#ff6600'
56 ctx.fill()
57
58 // Narysuj kratery
59 ctx.beginPath()
60 ctx.arc(180, 130, 20, 0, Math.PI * 2)
61 ctx.fillStyle = '#cc5500'
62 ctx.fill()
63
64 ctx.beginPath()
65 ctx.arc(220, 160, 15, 0, Math.PI * 2)
66 ctx.fill()
67
68 // Narysuj bazę NOVA LAB
69 ctx.fillStyle = '#00ff88'
70 ctx.fillRect(150, 220, 100, 40)
71
72 // Narysuj kopułę
73 ctx.beginPath()
74 ctx.arc(200, 220, 50, Math.PI, 0)
75 ctx.strokeStyle = '#00b4d8'
76 ctx.lineWidth = 3
77 ctx.stroke()
78
79 // Dodaj napis
80 ctx.fillStyle = '#00ff88'
81 ctx.font = 'bold 16px Courier'
82 ctx.fillText('NOVA LAB', 160, 245)
83
84 canvasDrawn.value = true
85 }
86}
87
88function incrementCounter() {
89 dynamicCounter.value++
90}
91
92onMounted(() => {
93 getContainerDimensions()
94
95 // Automatyczny fokus po zamontowaniu
96 setTimeout(() => {
97 focusInput()
98 }, 500)
99})
100</script>
101
102<template>
103 <div class="advanced-lab">
104 <h1>Zaawansowane Dyrektywy - NOVA LAB</h1>
105
106 <!-- Sekcja template refs -->
107 <section class="lab-section">
108 <h2>Template Refs - Dostęp do elementów DOM</h2>
109
110 <!-- Przykład: ref na polu input -->
111 <div class="demo-card">
112 <h3>ref na input - Fokus ustawiany z kodu</h3>
113 <div class="ref-demo">
114 <input
115 ref="inputRef"
116 type="text"
117 placeholder="Fokus w tym polu ustawisz z kodu..."
118 class="input-field"
119 >
120 <button @click="focusInput" class="btn btn-primary">
121 Ustaw fokus
122 </button>
123 </div>
124 <p class="info-text">Liczba wywołań focus: {{ focusCount }}</p>
125 <code class="code-hint">ref="inputRef" → inputRef.value.focus()</code>
126 </div>
127
128 <!-- Przykład: ref na kontenerze -->
129 <div class="demo-card">
130 <h3>ref na kontener - Odczyt wymiarów</h3>
131 <div ref="containerRef" class="ref-container">
132 <p>Ten kontener ma ref="containerRef"</p>
133 <p>Kliknij przycisk aby odczytać jego wymiary</p>
134 </div>
135 <button @click="getContainerDimensions" class="btn btn-success">
136 Odczytaj wymiary
137 </button>
138 <div v-if="containerInfo.width" class="dimensions-info">
139 <p>Szerokość: {{ containerInfo.width }}px</p>
140 <p>Wysokość: {{ containerInfo.height }}px</p>
141 <p>Pozycja: ({{ containerInfo.left }}px, {{ containerInfo.top }}px)</p>
142 </div>
143 </div>
144
145 <!-- Przykład: ref na canvasie -->
146 <div class="demo-card">
147 <h3>ref na canvas - Rysowanie</h3>
148 <canvas
149 ref="canvasRef"
150 width="400"
151 height="300"
152 class="canvas-field"
153 ></canvas>
154 <button @click="drawOnCanvas" class="btn btn-warning">
155 {{ canvasDrawn ? 'Przerysuj' : 'Narysuj' }} bazę na Marsie
156 </button>
157 <code class="code-hint">ref="canvasRef" → canvasRef.value.getContext('2d')</code>
158 </div>
159 </section>
160
161 <!-- Sekcja v-once -->
162 <section class="lab-section">
163 <h2>v-once - Jednorazowe renderowanie</h2>
164
165 <div class="demo-card">
166 <h3>Statyczne dane (v-once)</h3>
167 <div v-once class="static-content">
168 <p><strong>Data startu:</strong> {{ staticData.launchDate }}</p>
169 <p><strong>Kod misji:</strong> {{ staticData.missionCode }}</p>
170 <p><strong>Załoga startowa:</strong> {{ staticData.initialCrew }}</p>
171 <p class="info-text">Te dane są renderowane tylko raz i nie będą się aktualizować</p>
172 </div>
173 </div>
174
175 <div class="demo-card">
176 <h3>Porównanie: v-once i zwykłe wiązanie</h3>
177 <div class="comparison-grid">
178 <div class="comparison-item">
179 <h4>Z v-once (nie aktualizuje się)</h4>
180 <div v-once class="counter-display static">
181 {{ dynamicCounter }}
182 </div>
183 </div>
184 <div class="comparison-item">
185 <h4>Bez v-once (aktualizuje się)</h4>
186 <div class="counter-display dynamic">
187 {{ dynamicCounter }}
188 </div>
189 </div>
190 </div>
191 <button @click="incrementCounter" class="btn btn-primary">
192 Zwiększ licznik ({{ dynamicCounter }})
193 </button>
194 <p class="info-text">
195 Lewy licznik zostaje przy wartości początkowej, a prawy się aktualizuje
196 </p>
197 </div>
198 </section>
199
200 <!-- Sekcja v-cloak -->
201 <section class="lab-section">
202 <h2>v-cloak - Ukrywanie niezrenderowanych szablonów</h2>
203
204 <div class="demo-card">
205 <h3>v-cloak - Zapobiega pokazywaniu <code v-pre>{{ }}</code></h3>
206 <div v-cloak class="cloak-demo">
207 <p>Ta sekcja używa v-cloak</p>
208 <p>Przy powolnym ładowaniu użytkownik nie zobaczy surowych wąsów <code v-pre>{{ }}</code></p>
209 <p>Dane: {{ staticData.missionCode }}</p>
210 </div>
211 <code class="code-hint">
212 v-cloak + CSS: [v-cloak] { display: none; }
213 </code>
214 </div>
215 </section>
216
217 <!-- Ramka z podsumowaniem -->
218 <div class="info-box">
219 <h3>Zaawansowane dyrektywy Vue</h3>
220 <div class="info-grid">
221 <div>
222 <h4>Template Refs:</h4>
223 <ul>
224 <li><strong>ref="name"</strong> - Tworzy referencję do elementu</li>
225 <li><strong>const name = ref(null)</strong> - Deklaracja w script setup</li>
226 <li><strong>name.value</strong> - Dostęp do elementu DOM</li>
227 <li>Dostępne po onMounted()</li>
228 </ul>
229 </div>
230 <div>
231 <h4>v-once:</h4>
232 <ul>
233 <li>Renderuje element/komponent tylko raz</li>
234 <li>Pomija wszystkie kolejne aktualizacje</li>
235 <li>Optymalizacja dla statycznych treści</li>
236 <li>Przydatne dla dużych danych, które się nie zmieniają</li>
237 </ul>
238 </div>
239 <div>
240 <h4>v-cloak:</h4>
241 <ul>
242 <li>Ukrywa niezrenderowane szablony</li>
243 <li>Wymaga CSS: [v-cloak] { display: none; }</li>
244 <li>Zapobiega „błyskaniu” <code v-pre>{{ }}</code></li>
245 <li>Automatycznie usuwane po kompilacji</li>
246 </ul>
247 </div>
248 <div>
249 <h4>Zastosowania ref:</h4>
250 <ul>
251 <li>Ustawianie fokusu z kodu</li>
252 <li>Integracja z bibliotekami DOM</li>
253 <li>Rysowanie na canvasie i praca z SVG</li>
254 <li>Przewijanie do elementu</li>
255 <li>Odczyt wymiarów elementu</li>
256 </ul>
257 </div>
258 </div>
259 </div>
260 </div>
261</template>
262
263<style scoped>
264/* v-cloak CSS */
265[v-cloak] {
266 display: none;
267}
268
269.advanced-lab {
270 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
271 min-height: 100vh;
272 padding: 2rem;
273 color: #ffffff;
274 font-family: 'Courier New', monospace;
275}
276
277h1 {
278 text-align: center;
279 color: #00ff88;
280 text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
281 margin-bottom: 3rem;
282}
283
284h2 {
285 color: #00b4d8;
286 margin-bottom: 1.5rem;
287 border-bottom: 2px solid #00b4d8;
288 padding-bottom: 0.5rem;
289}
290
291h3 {
292 color: #00ff88;
293 font-size: 1.1rem;
294 margin-bottom: 1rem;
295}
296
297h4 {
298 color: #00b4d8;
299 font-size: 1rem;
300 margin-bottom: 0.75rem;
301}
302
303.lab-section {
304 background: rgba(0, 255, 136, 0.05);
305 border: 2px solid #00ff88;
306 border-radius: 10px;
307 padding: 2rem;
308 margin-bottom: 2rem;
309}
310
311.demo-card {
312 background: rgba(0, 180, 216, 0.1);
313 border: 2px solid #00b4d8;
314 border-radius: 8px;
315 padding: 1.5rem;
316 margin-bottom: 1.5rem;
317}
318
319.ref-demo {
320 display: flex;
321 gap: 1rem;
322 margin-bottom: 1rem;
323 flex-wrap: wrap;
324}
325
326.input-field {
327 background: rgba(0, 0, 0, 0.3);
328 border: 2px solid #00b4d8;
329 border-radius: 5px;
330 padding: 0.75rem;
331 color: #ffffff;
332 font-family: 'Courier New', monospace;
333 font-size: 1rem;
334 flex: 1;
335 min-width: 250px;
336}
337
338.input-field:focus {
339 outline: none;
340 border-color: #00ff88;
341 box-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
342 animation: focusPulse 0.5s ease;
343}
344
345@keyframes focusPulse {
346 0%, 100% { transform: scale(1); }
347 50% { transform: scale(1.02); }
348}
349
350.ref-container {
351 background: rgba(0, 255, 136, 0.1);
352 border: 2px dashed #00ff88;
353 border-radius: 8px;
354 padding: 2rem;
355 text-align: center;
356 margin: 1rem 0;
357}
358
359.dimensions-info {
360 background: rgba(0, 0, 0, 0.3);
361 border-left: 4px solid #00ff88;
362 border-radius: 5px;
363 padding: 1rem;
364 margin-top: 1rem;
365}
366
367.dimensions-info p {
368 margin: 0.5rem 0;
369 color: #00ff88;
370}
371
372.canvas-field {
373 display: block;
374 width: 100%;
375 max-width: 400px;
376 background: #0a0e27;
377 border: 2px solid #00b4d8;
378 border-radius: 8px;
379 margin: 1rem 0;
380}
381
382.static-content {
383 background: rgba(0, 255, 136, 0.1);
384 border: 2px solid #00ff88;
385 border-radius: 8px;
386 padding: 1.5rem;
387}
388
389.static-content p {
390 margin: 0.75rem 0;
391 color: #ffffff;
392}
393
394.static-content strong {
395 color: #00b4d8;
396}
397
398.comparison-grid {
399 display: grid;
400 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
401 gap: 1.5rem;
402 margin: 1.5rem 0;
403}
404
405.comparison-item {
406 background: rgba(0, 0, 0, 0.3);
407 border-radius: 8px;
408 padding: 1.5rem;
409 text-align: center;
410}
411
412.counter-display {
413 font-size: 3rem;
414 font-weight: bold;
415 padding: 2rem;
416 border-radius: 8px;
417 margin: 1rem 0;
418}
419
420.counter-display.static {
421 background: rgba(255, 180, 0, 0.2);
422 border: 2px solid #ffb400;
423 color: #ffb400;
424}
425
426.counter-display.dynamic {
427 background: rgba(0, 255, 136, 0.2);
428 border: 2px solid #00ff88;
429 color: #00ff88;
430 animation: pulse 2s infinite;
431}
432
433.cloak-demo {
434 background: rgba(0, 180, 216, 0.1);
435 border: 2px solid #00b4d8;
436 border-radius: 8px;
437 padding: 1.5rem;
438}
439
440.btn {
441 padding: 0.75rem 1.5rem;
442 border: none;
443 border-radius: 5px;
444 font-size: 1rem;
445 font-weight: bold;
446 cursor: pointer;
447 transition: all 0.3s ease;
448 font-family: 'Courier New', monospace;
449 margin: 0.5rem;
450}
451
452.btn-primary {
453 background: #00ff88;
454 color: #0a0e27;
455}
456
457.btn-success {
458 background: #00b4d8;
459 color: #ffffff;
460}
461
462.btn-warning {
463 background: #ffb400;
464 color: #0a0e27;
465}
466
467.btn:hover {
468 transform: scale(1.05);
469 box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
470}
471
472.code-hint {
473 display: block;
474 background: rgba(0, 0, 0, 0.5);
475 border-left: 4px solid #00ff88;
476 padding: 0.75rem;
477 border-radius: 5px;
478 color: #00ff88;
479 font-family: 'Courier New', monospace;
480 font-size: 0.9rem;
481 margin-top: 1rem;
482}
483
484.info-text {
485 color: #00b4d8;
486 font-size: 0.9rem;
487 margin-top: 1rem;
488}
489
490.info-box {
491 background: rgba(0, 255, 136, 0.05);
492 border: 2px solid #00ff88;
493 border-radius: 10px;
494 padding: 2rem;
495}
496
497.info-box h3 {
498 margin-top: 0;
499 margin-bottom: 1.5rem;
500}
501
502.info-grid {
503 display: grid;
504 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
505 gap: 2rem;
506}
507
508.info-box ul {
509 list-style: none;
510 padding: 0;
511}
512
513.info-box li {
514 padding: 0.5rem 0;
515 color: #ffffff;
516 border-left: 3px solid #00b4d8;
517 padding-left: 1rem;
518 margin: 0.5rem 0;
519}
520
521.info-box strong {
522 color: #00ff88;
523}
524
525@keyframes pulse {
526 0%, 100% { opacity: 1; }
527 50% { opacity: 0.7; }
528}
529</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. Jaki mechanizm Vue daje bezpośredni dostęp do elementu DOM z szablonu komponentu?
2. Jaka jest różnica między {{ }} a v-text?
3. Co robi v-pre?
4. Co robi dyrektywa v-once w Vue.js?
5. missionCode zmienia się z 'A-1' na 'B-2'. Co pokaże wtedy <p v-once>Kod: {{ missionCode }}</p>?
6. Po co używać v-cloak?
7. Dlaczego trzeba ostrożnie używać v-html?
8. Która dyrektywa Vue służy do optymalizacji wydajności przez memoizację?
9. Który modyfikator klawisza w Vue.js służy do wykrywania klawisza Escape?
10. Co robić gdy template staje się zbyt złożony?
11. Co robi v-memo w Vue 3?
12. Który zapis wywoła onSelf tylko wtedy, gdy kliknięto sam element, a nie jego dziecko?
13. Która praktyka jest zalecana w Vue templates?
Zadania praktyczne w grze
- Edytor kodu
Użyj template ref, aby po zamontowaniu komponentu automatycznie ustawić fokus w polu input.
- Układanie w pionie
Uporządkuj kroki użycia template ref:
- Układanie w pionie
Ułóż fragmenty kodu template ref w kolejności, w jakiej zaczynają działać:
- Klikanie w kolejności
Ułóż kod template ref z auto-focus:
- Układanie w pionie
Ułóż sposoby wiązania stylów od prostego do zaawansowanego:
- Układanie w pionie
Uporządkuj etapy działania v-if w Vue:
- Układanie w poziomie
Ułóż poprawną składnię v-for z dostępem do indeksu: