Kurs Vue.js · Moduł 2: Składnia szablonów

Template Refs

5 min czytania
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. 1. Jaki mechanizm Vue daje bezpośredni dostęp do elementu DOM z szablonu komponentu?

  2. 2. Jaka jest różnica między {{ }} a v-text?

  3. 3. Co robi v-pre?

  4. 4. Co robi dyrektywa v-once w Vue.js?

  5. 5. missionCode zmienia się z 'A-1' na 'B-2'. Co pokaże wtedy <p v-once>Kod: {{ missionCode }}</p>?

  6. 6. Po co używać v-cloak?

  7. 7. Dlaczego trzeba ostrożnie używać v-html?

  8. 8. Która dyrektywa Vue służy do optymalizacji wydajności przez memoizację?

  9. 9. Który modyfikator klawisza w Vue.js służy do wykrywania klawisza Escape?

  10. 10. Co robić gdy template staje się zbyt złożony?

  11. 11. Co robi v-memo w Vue 3?

  12. 12. Który zapis wywoła onSelf tylko wtedy, gdy kliknięto sam element, a nie jego dziecko?

  13. 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:

Przydatne artykuły