Cours Vue.js · Module 15: TypeScript et Nuxt
Typer les emits
Dans cette leçon5
Tout comme les props définissent les données d'entrée d'un composant, les emits définissent ses événements de sortie. Avec TypeScript, on peut typer précisément quels événements un composant émet et quelles données chaque événement transporte.
defineEmits avec type générique
1<script setup lang="ts">
2// Syntaxe générique
3const emit = defineEmits<{
4 (e: 'update', value: string): void
5 (e: 'delete', id: number): void
6 (e: 'close'): void
7}>()
8
9// Utilisation
10emit('update', 'new value') // OK
11emit('delete', 42) // OK
12emit('close') // OK
13// emit('update', 123) // ERREUR ! Une chaîne est attendue, pas un nombre
14// emit('unknown') // ERREUR ! Événement inconnu
15</script>Nouvelle syntaxe 3.3+
Depuis Vue 3.3, une syntaxe plus courte est disponible :
1<script setup lang="ts">
2const emit = defineEmits<{
3 update: [value: string]
4 delete: [id: number]
5 close: []
6}>()
7</script>Les deux syntaxes sont équivalentes. La nouvelle est plus concise - on utilise le nom de l'événement comme clé et les arguments comme un tuple.
Les événements typés en pratique
Voici l'exemple d'un panneau de contrôle de la station :
1<script setup lang="ts">
2interface ModuleEvent {
3 moduleId: string
4 timestamp: number
5}
6
7const emit = defineEmits<{
8 activate: [event: ModuleEvent]
9 deactivate: [event: ModuleEvent]
10 alert: [level: 'low' | 'medium' | 'high', message: string]
11}>()
12
13function handleActivation(moduleId: string) {
14 emit('activate', {
15 moduleId,
16 timestamp: Date.now()
17 })
18}
19
20function triggerAlert() {
21 emit('alert', 'high', 'Quantum anomaly detected!')
22}
23</script>Communication parent-enfant
Le composant enfant émet des événements, le parent les écoute :
1<!-- ControlPanel.vue (enfant) -->
2<script setup lang="ts">
3const emit = defineEmits<{
4 command: [action: string, target: string]
5}>()
6</script>
7
8<template>
9 <button @click="emit('command', 'scan', 'sector-7')">
10 Scan Sector 7
11 </button>
12</template>1<!-- App.vue (parent) -->
2<template>
3 <ControlPanel @command="handleCommand" />
4</template>
5
6<script setup lang="ts">
7function handleCommand(action: string, target: string) {
8 console.log(action, target)
9 // TypeScript connaît les types des arguments !
10}
11</script>Combiner props et emits
Le pattern v-model avec des types :
1<script setup lang="ts">
2const props = defineProps<{
3 modelValue: string
4}>()
5
6const emit = defineEmits<{
7 'update:modelValue': [value: string]
8}>()
9
10function onInput(event: Event) {
11 const target = event.target as HTMLInputElement
12 emit('update:modelValue', target.value)
13}
14</script>
15
16<template>
17 <input :value="modelValue" @input="onInput" />
18</template>Code de cette leçon : App.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3import CommandPanel from './CommandPanel.vue'
4
5interface CommandLog {
6 action: string
7 target: string
8 timestamp: string
9}
10
11const logs = ref<CommandLog[]>([])
12
13function handleCommand(action: string, target: string) {
14 logs.value.unshift({
15 action,
16 target,
17 timestamp: new Date().toLocaleTimeString()
18 })
19}
20
21function handleAlert(level: 'low' | 'medium' | 'high', message: string) {
22 logs.value.unshift({
23 action: 'ALERT [' + level + ']',
24 target: message,
25 timestamp: new Date().toLocaleTimeString()
26 })
27}
28</script>
29
30<template>
31 <div class="nova-lab">
32 <h1>NOVA LAB - Emits typés</h1>
33
34 <CommandPanel
35 @command="handleCommand"
36 @alert="handleAlert"
37 />
38
39 <div class="log-section">
40 <h2>Command log</h2>
41 <div v-for="(log, i) in logs" :key="i" class="log-entry">
42 <span class="time">{{ log.timestamp }}</span>
43 <span class="action">{{ log.action }}</span>
44 <span class="target">{{ log.target }}</span>
45 </div>
46 <p v-if="logs.length === 0" class="empty">No commands</p>
47 </div>
48 </div>
49</template>
50
51<style scoped>
52.nova-lab {
53 background: #0a0e27;
54 color: #00ff88;
55 min-height: 100vh;
56 padding: 20px;
57 font-family: monospace;
58}
59h1 { color: #00ff88; margin-bottom: 15px; }
60h2 { color: #00b4d8; margin-top: 20px; }
61.log-section { margin-top: 15px; }
62.log-entry {
63 display: flex;
64 gap: 12px;
65 padding: 8px 12px;
66 margin: 4px 0;
67 background: rgba(0,180,216,0.06);
68 border-radius: 6px;
69 font-size: 13px;
70}
71.time { color: #666; }
72.action { color: #00b4d8; font-weight: bold; }
73.target { color: #00ff88; }
74.empty { color: #555; font-style: italic; }
75</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. Quelle syntaxe définit correctement des emits typés dans Vue 3.3+ ?
2. Étant donné
const emit = defineEmits<{ alert: [level: string, msg: string] }>(), quel appel est correct ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Clique dans l'ordre
Ordonne les étapes pour implémenter un v-model typé dans un composant :
- Tri vertical
Ordonne le flux de données dans une communication parent-enfant typée :