Cours Vue.js · Module 2: Syntaxe des templates

Template Refs

6 min de lecture
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. 1. Quel mécanisme de Vue donne un accès direct à un élément du DOM depuis le template du composant ?

  2. 2. Quelle est la différence entre {{ }} et v-text ?

  3. 3. Que fait v-pre ?

  4. 4. Que fait la directive v-once dans Vue.js ?

  5. 5. missionCode passe de 'A-1' à 'B-2'. Qu'affiche alors <p v-once>Code : {{ missionCode }}</p> ?

  6. 6. Pourquoi utiliserais-tu v-cloak ?

  7. 7. Pourquoi dois-tu être prudent en utilisant v-html ?

  8. 8. Quelle directive Vue est utilisée pour l'optimisation des performances par mémoïsation ?

  9. 9. Quel modificateur de touche dans Vue.js est utilisé pour détecter la touche Échap ?

  10. 10. Que dois-tu faire quand un template devient trop complexe ?

  11. 11. Que fait v-memo dans Vue 3 ?

  12. 12. Quelle écriture appelle onSelf uniquement quand on clique sur l'élément lui-même, et non sur son enfant ?

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