Cours Vue.js · Module 15: TypeScript et Nuxt
Typer ref, reactive et computed
Dans cette leçon5
Dans les systèmes de la station spatiale, chaque valeur a son type précis. La Composition API de Vue 3 offre un excellent support de TypeScript - ref, reactive et computed peuvent être entièrement typés.
Typer ref
ref accepte un type générique Ref<T> :
1<script setup lang="ts">
2import { ref, type Ref } from 'vue'
3
4// Inférence de type
5const count = ref(0) // Ref<number>
6const name = ref('NOVA LAB') // Ref<string>
7
8// Typage explicite
9const status = ref<string>('active')
10const data = ref<number[]>([1, 2, 3])
11
12// Type union - utile quand la valeur peut être null
13const selectedId = ref<string | null>(null)
14
15// Type Ref explicite
16const temperature: Ref<number> = ref(-63)
17</script>Typer reactive
reactive infère automatiquement les types à partir de l'objet :
1<script setup lang="ts">
2import { reactive } from 'vue'
3
4interface StationState {
5 name: string
6 power: number
7 modules: string[]
8 coordinates: { x: number; y: number; z: number }
9}
10
11const station = reactive<StationState>({
12 name: 'NOVA LAB',
13 power: 100,
14 modules: ['Lab', 'Bridge', 'Hangar'],
15 coordinates: { x: 0, y: 0, z: 0 }
16})
17
18// TypeScript connaît la structure exacte
19station.power = 95 // OK
20// station.power = 'high' // ERREUR ! un number est attendu
21// station.unknown = true // ERREUR ! Propriété inconnue
22</script>Typer computed
computed infère automatiquement le type de retour :
1<script setup lang="ts">
2import { ref, computed } from 'vue'
3
4const power = ref(85)
5const maxPower = ref(100)
6
7// Type inféré : ComputedRef<number>
8const powerPercentage = computed(() => {
9 return (power.value / maxPower.value) * 100
10})
11
12// Type inféré : ComputedRef<string>
13const powerStatus = computed(() => {
14 if (power.value > 80) return 'optimal'
15 if (power.value > 40) return 'moderate'
16 return 'critical'
17})
18
19// Typage explicite
20const label = computed<string>(() => {
21 return 'Power: ' + powerPercentage.value.toFixed(1) + '%'
22})
23</script>computed modifiable (writable) avec des types
1<script setup lang="ts">
2import { ref, computed } from 'vue'
3
4const celsius = ref(20)
5
6const fahrenheit = computed<number>({
7 get: () => celsius.value * 9/5 + 32,
8 set: (val: number) => {
9 celsius.value = (val - 32) * 5/9
10 }
11})
12
13fahrenheit.value = 68 // celsius devient 20
14</script>Typer les tableaux réactifs
1<script setup lang="ts">
2import { ref } from 'vue'
3
4interface LogEntry {
5 id: number
6 message: string
7 level: 'info' | 'warn' | 'error'
8 timestamp: Date
9}
10
11const logs = ref<LogEntry[]>([])
12
13function addLog(message: string, level: LogEntry['level']) {
14 logs.value.push({
15 id: Date.now(),
16 message,
17 level,
18 timestamp: new Date()
19 })
20}
21
22// TypeScript vérifie la validité des arguments
23addLog('System started', 'info') // OK
24// addLog('Error', 'critical') // ERREUR ! 'critical' n'est pas valide
25</script>Code de cette leçon : App.vue
1<script setup lang="ts">
2import { ref, reactive, computed, type Ref } from 'vue'
3
4// --- Typer ref ---
5const power = ref<number>(85)
6const stationName = ref<string>('NOVA LAB')
7const selectedModule = ref<string | null>(null)
8
9// --- Typer reactive ---
10interface SystemStatus {
11 oxygen: number
12 temperature: number
13 pressure: number
14 alerts: string[]
15}
16
17const systemStatus = reactive<SystemStatus>({
18 oxygen: 98.5,
19 temperature: -63,
20 pressure: 0.6,
21 alerts: []
22})
23
24// --- Typer computed ---
25const powerStatus = computed<string>(() => {
26 if (power.value > 80) return 'Optimal'
27 if (power.value > 40) return 'Modéré'
28 return 'Critique'
29})
30
31const powerColor = computed(() => {
32 if (power.value > 80) return '#00ff88'
33 if (power.value > 40) return '#f39c12'
34 return '#e74c3c'
35})
36
37const oxygenWarning = computed<boolean>(() => {
38 return systemStatus.oxygen < 90
39})
40
41// Tableau de logs typé
42interface LogEntry {
43 id: number
44 message: string
45 level: 'info' | 'warn' | 'error'
46}
47const logs = ref<LogEntry[]>([])
48let logId = 0
49
50function addLog(message: string, level: LogEntry['level']) {
51 logs.value.push({ id: logId++, message, level })
52}
53
54function adjustPower(delta: number) {
55 power.value = Math.max(0, Math.min(100, power.value + delta))
56 addLog('Power: ' + power.value + '%', power.value < 30 ? 'error' : 'info')
57}
58
59function adjustOxygen(delta: number) {
60 systemStatus.oxygen = Math.max(0, Math.min(100, systemStatus.oxygen + delta))
61 addLog('O2: ' + systemStatus.oxygen + '%', systemStatus.oxygen < 90 ? 'warn' : 'info')
62}
63</script>
64
65<template>
66 <div class="nova-lab">
67 <h1>{{ stationName }} - ref, reactive, computed</h1>
68
69 <div class="grid">
70 <div class="panel">
71 <h3>Énergie</h3>
72 <div class="value" :style="{ color: powerColor }">{{ power }}%</div>
73 <div class="label">{{ powerStatus }}</div>
74 <div class="controls">
75 <button @click="adjustPower(-10)" class="btn danger">-10</button>
76 <button @click="adjustPower(10)" class="btn success">+10</button>
77 </div>
78 </div>
79
80 <div class="panel">
81 <h3>Oxygène</h3>
82 <div class="value" :class="{ warning: oxygenWarning }">{{ systemStatus.oxygen }}%</div>
83 <div class="label">{{ oxygenWarning ? 'Warning! Low level' : 'Normal' }}</div>
84 <div class="controls">
85 <button @click="adjustOxygen(-5)" class="btn danger">-5</button>
86 <button @click="adjustOxygen(5)" class="btn success">+5</button>
87 </div>
88 </div>
89 </div>
90
91 <div class="log-panel" v-if="logs.length > 0">
92 <div v-for="log in logs" :key="log.id" :class="['log', log.level]">
93 [{{ log.level.toUpperCase() }}] {{ log.message }}
94 </div>
95 </div>
96 </div>
97</template>
98
99<style scoped>
100.nova-lab {
101 background: #0a0e27;
102 color: #00ff88;
103 min-height: 100vh;
104 padding: 20px;
105 font-family: monospace;
106}
107h1 { color: #00ff88; margin-bottom: 15px; font-size: 18px; }
108h3 { color: #00b4d8; margin-bottom: 8px; }
109.grid { display: flex; gap: 15px; }
110.panel {
111 flex: 1;
112 background: rgba(0,180,216,0.08);
113 border: 2px solid #00b4d8;
114 padding: 16px;
115 border-radius: 12px;
116 text-align: center;
117}
118.value { font-size: 32px; font-weight: bold; }
119.value.warning { color: #e74c3c; }
120.label { font-size: 12px; color: #888; margin: 4px 0 12px; }
121.controls { display: flex; gap: 8px; justify-content: center; }
122.btn {
123 border: none;
124 color: white;
125 padding: 6px 14px;
126 border-radius: 6px;
127 cursor: pointer;
128}
129.success { background: #00ff88; color: #0a0e27; }
130.danger { background: #e74c3c; }
131.log-panel { margin-top: 15px; }
132.log {
133 padding: 4px 10px;
134 font-size: 12px;
135 margin: 2px 0;
136 border-radius: 4px;
137}
138.log.info { color: #00b4d8; }
139.log.warn { color: #f39c12; }
140.log.error { color: #e74c3c; }
141</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. Comment type-t-on correctement un ref qui peut être string ou null ?
2. TypeScript infère-t-il automatiquement le type de retour de computed ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Type ref, computed et l'interface SystemLog.
- Éditeur de code
Crée un état de station typé avec une interface et reactive<StationState>().
- Clique dans l'ordre
Ordonne les éléments d'un computed modifiable (writable) avec des types :
- Éditeur de code
Définis un template ref avec le bon type et utilise-le dans onMounted.
- Tri horizontal
Ordonne la syntaxe correcte de reactive avec une interface :