Cours Vue.js · Module 2: Syntaxe des templates
Template Refs
Dans cette leçon5
Parfois, tu as besoin d'un accès direct à un élément du DOM - comme un ingénieur qui doit vérifier un capteur physique.
Les templates décrivent l'apparence du panneau, et Vue s'occupe lui-même des éléments de la page. Certaines tâches ne s'écrivent pourtant pas de façon déclarative : placer le curseur dans le champ de commande juste après l'ouverture du panneau, mesurer un conteneur ou confier un élément <canvas> à une bibliothèque de graphiques externe. Il te faut alors une prise sur le véritable élément - et c'est ce que t'offrent les template refs. Dans la seconde partie de la leçon, tu découvriras aussi quelques directives spéciales pour les tâches inhabituelles.
Template Refs de base
L'attribut spécial ref marque un élément dans le template. Dans le script, tu déclares un ref du même nom avec la valeur null, et une fois le composant monté, Vue y place l'élément. Le moment du montage est capturé par la fonction onMounted - un hook du cycle de vie que Vue appelle quand le composant est déjà sur la page ; nous verrons le cycle de vie dans le module 6.
1<template>
2 <div class="input-panel" style="background: #0a0e27; padding: 1rem;">
3 <input ref="inputRef" type="text" placeholder="Module code"
4 style="background: #0a0e27; border: 1px solid #00b4d8; color: #00ff88; padding: 0.5rem;">
5 <button @click="focusInput" class="btn-focus">Set focus</button>
6 </div>
7</template>
8
9<script setup>
10import { ref, onMounted } from 'vue'
11
12// Le nom doit correspondre à l'attribut ref dans le template
13const inputRef = ref(null)
14
15function focusInput() {
16 inputRef.value?.focus()
17}
18
19onMounted(() => {
20 // L'élément est disponible après le montage
21 inputRef.value?.focus()
22})
23</script>L'ordre est toujours le même : la déclaration ref(null), l'attribut ref="inputRef" sur l'élément, le montage, et seulement ensuite l'utilisation. Avant le montage, et aussi après la suppression de l'élément par v-if, le ref contient null, c'est pourquoi ?. te protège d'une erreur. C'est la même fonction ref() que pour les données - le conteneur garde simplement ici un élément au lieu d'un nombre.
Depuis Vue 3.5, la méthode recommandée est l'assistant useTemplateRef. Son argument doit correspondre à la valeur de l'attribut ref, et la variable peut porter n'importe quel nom :
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="Code du module">
13</template>Cela fonctionne de façon identique, et en TypeScript le type de l'élément est déduit automatiquement. L'ancienne écriture fonctionne toujours, et tu la rencontreras dans du code existant et dans les exercices, mais pour les nouveaux projets je te recommande useTemplateRef.
Refs dans v-for
Quand ref se trouve sur un élément avec v-for, tu obtiens après le montage un tableau d'éléments, et non un seul élément :
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) // Tableau d'éléments LI
24})
25</script>La console affiche deux éléments li. La documentation précise cependant que l'ordre de ce tableau ne correspond pas forcément à l'ordre des données, donc ne suppose pas que l'élément d'index 0 est le premier module.
Function Refs
Au lieu d'un nom, tu peux passer une fonction grâce à la liaison dynamique :ref. Vue l'appelle avec l'élément à chaque mise à jour du composant, et avec null quand l'élément est démonté :
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 set:', el)
14}
15</script>C'est toi qui décides où ranger l'élément, par exemple dans une map indexée par identifiant. Pense seulement au cas null.
Refs sur les composants
Un attribut ref sur un composant donne accès à son instance. SensorPanel.vue est un fichier de composant séparé, importé comme n'importe quel module - nous nous occuperons des composants dans le module 6. Le parent appelle la méthode de l'enfant après le montage :
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 // Accès aux méthodes et propriétés du composant
13 sensorRef.value?.refreshData()
14})
15</script>Les composants avec <script setup> sont privés par défaut : le parent ne voit rien tant que l'enfant ne l'expose pas lui-même avec la macro defineExpose, qu'il n'est pas nécessaire d'importer :
1<!-- SensorPanel.vue - doit exposer ses méthodes -->
2<script setup>
3import { ref } from 'vue'
4
5const sensorData = ref({})
6
7function refreshData() {
8 console.log('Refreshing sensor data!')
9}
10
11// defineExpose - rend disponible au parent
12defineExpose({
13 refreshData,
14 sensorData
15})
16</script>defineExpose doit être appelée avant le premier await. Ce genre de connexion lie étroitement les composants, alors utilise-le avec parcimonie - les props et les événements sont généralement un meilleur choix.
Directives spéciales
Quelques directives intégrées résolvent des tâches plus rares, mais elles valent la peine d'être connues. v-text définit le textContent de l'élément, donc elle remplace tout son contenu, alors que les moustaches ne remplacent que leur propre fragment :
1<template>
2 <p v-text="statusMessage"></p>
3 <p>Statut : {{ statusMessage }}</p>
4</template>Les deux paragraphes affichent le message, mais seul le second garde l'étiquette Statut. v-once rend un élément une seule fois, puis le traite comme statique. v-memo (Vue 3.2+) mémorise un fragment du template et ne le met à jour que si l'une des valeurs de son tableau change :
1<template>
2 <p v-once>Code de mission : {{ missionCode }}</p>
3
4 <div v-for="module in modules" :key="module.id" v-memo="[module.id === selectedId]">
5 {{ module.name }} - sélectionné : {{ module.id === selectedId }}
6 </div>
7</template>Un changement ultérieur de missionCode ne rafraîchira pas le paragraphe. Après un changement de selectedId, seuls les deux modules dont l'indicateur a changé sont redessinés. v-memo="[]" agit comme v-once, et des dépendances mal choisies laissent des données périmées à l'écran, c'est pourquoi c'est un outil pour les très longues listes. Les deux dernières directives concernent la compilation :
1<template>
2 <code v-pre>{{ ceci ne sera pas compilé }}</code>
3</template>
4
5<style>
6[v-cloak] {
7 display: none;
8}
9</style>v-pre saute la compilation de l'élément et de ses enfants, donc tu vois les moustaches telles quelles. v-cloak n'est nécessaire que sans étape de build, quand Vue compile un template écrit directement dans le HTML : avec cette règle CSS, il masque les {{ }} bruts jusqu'au montage de l'application. Dans un projet Vite, tu n'en as pas besoin.
Considère les template refs comme un dernier recours - vérifie d'abord si le template suffit. Dans le projet du module, tu combineras tous ces outils dans un seul panneau d'hologrammes.
Retiens : un template ref est une clé de maintenance pour un seul appareil - tu ne t'en sers qu'après le montage, et seulement quand le panneau de commande ne suffit pas.
Code de cette leçon : App.vue
1<script setup>
2import { ref, onMounted } from 'vue'
3
4// Template refs
5const inputRef = ref(null)
6const containerRef = ref(null)
7const canvasRef = ref(null)
8
9// Données
10const focusCount = ref(0)
11const containerInfo = ref({})
12const canvasDrawn = ref(false)
13
14// Données réactives pour la démonstration de 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// Fonctions
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 // Effacer le canvas
49 ctx.fillStyle = '#0a0e27'
50 ctx.fillRect(0, 0, canvas.width, canvas.height)
51
52 // Dessiner Mars
53 ctx.beginPath()
54 ctx.arc(200, 150, 80, 0, Math.PI * 2)
55 ctx.fillStyle = '#ff6600'
56 ctx.fill()
57
58 // Dessiner les cratères
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 // Dessiner la base NOVA LAB
69 ctx.fillStyle = '#00ff88'
70 ctx.fillRect(150, 220, 100, 40)
71
72 // Dessiner le dôme
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 // Ajouter du texte
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 // Focus automatique au montage
96 setTimeout(() => {
97 focusInput()
98 }, 500)
99})
100</script>
101
102<template>
103 <div class="advanced-lab">
104 <h1>Directives avancées - NOVA LAB</h1>
105
106 <!-- Section Template Refs -->
107 <section class="lab-section">
108 <h2>Template Refs - Accès aux éléments du DOM</h2>
109
110 <!-- Exemple de ref sur un input -->
111 <div class="demo-card">
112 <h3>ref sur input - Focus programmatique</h3>
113 <div class="ref-demo">
114 <input
115 ref="inputRef"
116 type="text"
117 placeholder="Cet input peut recevoir le focus de façon programmatique..."
118 class="input-field"
119 >
120 <button @click="focusInput" class="btn btn-primary">
121 Donner le focus à l'input
122 </button>
123 </div>
124 <p class="info-text">Nombre d'appels de focus : {{ focusCount }}</p>
125 <code class="code-hint">ref="inputRef" → inputRef.value.focus()</code>
126 </div>
127
128 <!-- Exemple de ref sur un conteneur -->
129 <div class="demo-card">
130 <h3>ref sur un conteneur - Lecture des dimensions</h3>
131 <div ref="containerRef" class="ref-container">
132 <p>Ce conteneur a ref="containerRef"</p>
133 <p>Clique sur le bouton pour lire ses dimensions</p>
134 </div>
135 <button @click="getContainerDimensions" class="btn btn-success">
136 Obtenir les dimensions
137 </button>
138 <div v-if="containerInfo.width" class="dimensions-info">
139 <p>Largeur : {{ containerInfo.width }}px</p>
140 <p>Hauteur : {{ containerInfo.height }}px</p>
141 <p>Position : ({{ containerInfo.left }}px, {{ containerInfo.top }}px)</p>
142 </div>
143 </div>
144
145 <!-- Exemple de ref sur un canvas -->
146 <div class="demo-card">
147 <h3>ref sur canvas - Dessin</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 ? 'Redessiner' : 'Dessiner' }} la base martienne
156 </button>
157 <code class="code-hint">ref="canvasRef" → canvasRef.value.getContext('2d')</code>
158 </div>
159 </section>
160
161 <!-- Section v-once -->
162 <section class="lab-section">
163 <h2>v-once - Rendu unique</h2>
164
165 <div class="demo-card">
166 <h3>Données statiques (v-once)</h3>
167 <div v-once class="static-content">
168 <p><strong>Date de lancement :</strong> {{ staticData.launchDate }}</p>
169 <p><strong>Code de mission :</strong> {{ staticData.missionCode }}</p>
170 <p><strong>Équipage initial :</strong> {{ staticData.initialCrew }}</p>
171 <p class="info-text">Ces données ne sont rendues qu'une seule fois et ne se mettront pas à jour</p>
172 </div>
173 </div>
174
175 <div class="demo-card">
176 <h3>Comparaison : v-once vs liaison normale</h3>
177 <div class="comparison-grid">
178 <div class="comparison-item">
179 <h4>Avec v-once (ne se met pas à jour)</h4>
180 <div v-once class="counter-display static">
181 {{ dynamicCounter }}
182 </div>
183 </div>
184 <div class="comparison-item">
185 <h4>Sans v-once (se met à jour)</h4>
186 <div class="counter-display dynamic">
187 {{ dynamicCounter }}
188 </div>
189 </div>
190 </div>
191 <button @click="incrementCounter" class="btn btn-primary">
192 Incrémenter le compteur ({{ dynamicCounter }})
193 </button>
194 <p class="info-text">
195 Le compteur de gauche reste à la valeur initiale, celui de droite se met à jour
196 </p>
197 </div>
198 </section>
199
200 <!-- Section v-cloak -->
201 <section class="lab-section">
202 <h2>v-cloak - Masquer les templates non rendus</h2>
203
204 <div class="demo-card">
205 <h3>v-cloak - Empêche l'affichage de <code v-pre>{{ }}</code></h3>
206 <div v-cloak class="cloak-demo">
207 <p>Cette section utilise v-cloak</p>
208 <p>En cas de chargement lent, l'utilisateur ne verra pas les moustaches brutes <code v-pre>{{ }}</code></p>
209 <p>Données : {{ 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 <!-- Encadré d'information -->
218 <div class="info-box">
219 <h3>Directives Vue avancées</h3>
220 <div class="info-grid">
221 <div>
222 <h4>Template Refs :</h4>
223 <ul>
224 <li><strong>ref="name"</strong> - Crée une référence vers un élément</li>
225 <li><strong>const name = ref(null)</strong> - Déclaration dans setup</li>
226 <li><strong>name.value</strong> - Accès à l'élément du DOM</li>
227 <li>Disponible après onMounted()</li>
228 </ul>
229 </div>
230 <div>
231 <h4>v-once :</h4>
232 <ul>
233 <li>Rend l'élément/composant une seule fois</li>
234 <li>Ignore toutes les mises à jour ultérieures</li>
235 <li>Optimisation pour le contenu statique</li>
236 <li>Utile pour de grandes données qui ne changent pas</li>
237 </ul>
238 </div>
239 <div>
240 <h4>v-cloak :</h4>
241 <ul>
242 <li>Masque les templates non rendus</li>
243 <li>Nécessite du CSS : [v-cloak] { display: none; }</li>
244 <li>Empêche le « clignotement » de <code v-pre>{{ }}</code></li>
245 <li>Retiré automatiquement après la compilation</li>
246 </ul>
247 </div>
248 <div>
249 <h4>Usages de ref :</h4>
250 <ul>
251 <li>Focus programmatique sur un input</li>
252 <li>Intégration avec des bibliothèques DOM</li>
253 <li>Manipulation de canvas et SVG</li>
254 <li>Défilement vers un élément</li>
255 <li>Lecture des dimensions d'un élément</li>
256 </ul>
257 </div>
258 </div>
259 </div>
260 </div>
261</template>
262
263<style scoped>
264/* CSS v-cloak */
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>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Quel mécanisme de Vue donne un accès direct à un élément du DOM depuis le template du composant ?
2. Quelle est la différence entre {{ }} et v-text ?
3. Que fait v-pre ?
4. Que fait la directive v-once dans Vue.js ?
5. missionCode passe de 'A-1' à 'B-2'. Qu'affiche alors <p v-once>Code : {{ missionCode }}</p> ?
6. Pourquoi utiliserais-tu v-cloak ?
7. Pourquoi dois-tu être prudent en utilisant v-html ?
8. Quelle directive Vue est utilisée pour l'optimisation des performances par mémoïsation ?
9. Quel modificateur de touche dans Vue.js est utilisé pour détecter la touche Échap ?
10. Que dois-tu faire quand un template devient trop complexe ?
11. Que fait v-memo dans Vue 3 ?
12. Quelle écriture appelle onSelf uniquement quand on clique sur l'élément lui-même, et non sur son enfant ?
13. Quelle pratique est recommandée dans les templates Vue ?
Tâches pratiques dans le jeu
- Éditeur de code
Utilise un template ref pour donner automatiquement le focus à un input après le montage du composant.
- Tri vertical
Ordonne les étapes de l'utilisation d'un template ref :
- Tri vertical
Ordonne les morceaux de code du template ref dans l'ordre où ils prennent effet :
- Clique dans l'ordre
Ordonne le code d'un template ref avec focus automatique :
- Tri vertical
Ordonne les méthodes de liaison de style de la plus simple à la plus avancée :
- Tri vertical
Ordonne les étapes de v-if dans Vue :
- Tri horizontal
Ordonne la syntaxe correcte de v-for avec accès à l'index :