Cours Vue.js · Module 15: TypeScript et Nuxt

Template refs et Provide/Inject avec des types

3 min de lecture
Dans cette leçon4

Dans les systèmes avancés de la station NOVA LAB, il nous faut parfois référencer directement des éléments du DOM ou transmettre des données entre composants sans props drilling. TypeScript nous aide à le faire en toute sécurité.

Les template refs avec des types

Un template ref permet d'obtenir une référence vers un élément du DOM :

1<script setup lang="ts">
2import { ref, onMounted } from 'vue'
3
4// Type d'élément HTML - le ref peut être null avant le montage
5const inputRef = ref<HTMLInputElement | null>(null)
6const canvasRef = ref<HTMLCanvasElement | null>(null)
7
8onMounted(() => {
9  // Après le montage, le ref n'est plus null
10  inputRef.value?.focus()
11
12  const ctx = canvasRef.value?.getContext('2d')
13  if (ctx) {
14    ctx.fillStyle = '#00ff88'
15    ctx.fillRect(0, 0, 100, 100)
16  }
17})
18</script>
19
20<template>
21  <input ref="inputRef" placeholder="Enter command..." />
22  <canvas ref="canvasRef" width="400" height="300" />
23</template>

Le type HTMLInputElement | null indique à TypeScript que le ref peut être null (avant que le composant soit monté).

Références de composant

Tu peux aussi obtenir une référence vers un composant enfant :

1<script setup lang="ts">
2import { ref } from 'vue'
3import SensorPanel from './SensorPanel.vue'
4
5// InstanceType<typeof Component> donne le type de l'instance du composant
6const sensorRef = ref<InstanceType<typeof SensorPanel> | null>(null)
7
8function refreshSensors() {
9  // Accès aux méthodes publiques du composant
10  sensorRef.value?.refresh()
11}
12</script>
13
14<template>
15  <SensorPanel ref="sensorRef" />
16  <button @click="refreshSensors">Refresh Sensors</button>
17</template>

InjectionKey et Provide/Inject

provide/inject permet de transmettre des données vers le bas de l'arbre de composants. InjectionKey<T> garantit la sécurité des types :

1<!-- types.ts -->
2<script lang="ts">
3import type { InjectionKey, Ref } from 'vue'
4
5export interface StationConfig {
6  name: string
7  maxCrew: number
8  sector: string
9}
10
11// Clé avec type - garantit que provide et inject utilisent le même type
12export const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
13export const ThemeKey: InjectionKey<Ref<'dark' | 'light'>> = Symbol('Theme')
14</script>
1<!-- Provider (parent) -->
2<script setup lang="ts">
3import { provide, ref } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6const theme = ref<'dark' | 'light'>('dark')
7
8provide(StationConfigKey, {
9  name: 'NOVA LAB',
10  maxCrew: 100,
11  sector: 'Alpha-7'
12})
13
14provide(ThemeKey, theme)
15</script>
1<!-- Consumer (enfant/petit-enfant) -->
2<script setup lang="ts">
3import { inject } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6// TypeScript connaît le type ! StationConfig | undefined
7const config = inject(StationConfigKey)
8
9// Avec une valeur par défaut - ce n'est pas undefined
10const theme = inject(ThemeKey, ref('dark'))
11
12// On utilise l'optional chaining car config peut être undefined
13console.log(config?.name)    // 'NOVA LAB'
14console.log(theme.value)     // 'dark'
15</script>

Pattern avec valeur par défaut

Pour éviter undefined, tu peux utiliser une valeur par défaut :

1const config = inject(StationConfigKey, {
2  name: 'Unknown',
3  maxCrew: 0,
4  sector: 'N/A'
5})
6// Maintenant config est toujours StationConfig (jamais undefined)

Tu peux aussi utiliser une fonction factory :

1const config = inject(StationConfigKey, () => ({
2  name: 'Default',
3  maxCrew: 50,
4  sector: 'Beta'
5}), true)  // le troisième argument = true signifie que c'est une fonction factory
Code de cette leçon : App.vue
1<script setup lang="ts">
2import { ref, provide, onMounted, type InjectionKey } from 'vue'
3import InfoPanel from './InfoPanel.vue'
4
5// --- Template Ref ---
6const searchInput = ref<HTMLInputElement | null>(null)
7const searchQuery = ref('')
8
9onMounted(() => {
10  // Après le montage - focus sur l'input
11  searchInput.value?.focus()
12})
13
14function clearSearch() {
15  searchQuery.value = ''
16  searchInput.value?.focus()
17}
18
19// --- InjectionKey ---
20interface StationConfig {
21  name: string
22  sector: string
23  year: number
24}
25
26const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
27
28// Provide avec type
29provide(StationConfigKey, {
30  name: 'NOVA LAB',
31  sector: 'Alpha-7',
32  year: 2087
33})
34
35// On exporte la clé pour que l'enfant puisse l'utiliser
36provide('configKey', StationConfigKey)
37</script>
38
39<template>
40  <div class="nova-lab">
41    <h1>Template Refs & Provide/Inject</h1>
42
43    <div class="search-box">
44      <input
45        ref="searchInput"
46        v-model="searchQuery"
47        placeholder="Search station systems..."
48        class="search-input"
49      />
50      <button @click="clearSearch" class="btn">Effacer</button>
51    </div>
52
53    <p v-if="searchQuery" class="query-info">
54      Recherche : <strong>{{ searchQuery }}</strong>
55    </p>
56
57    <InfoPanel />
58  </div>
59</template>
60
61<style scoped>
62.nova-lab {
63  background: #0a0e27;
64  color: #00ff88;
65  min-height: 100vh;
66  padding: 20px;
67  font-family: monospace;
68}
69h1 { color: #00ff88; margin-bottom: 15px; }
70.search-box { display: flex; gap: 8px; margin-bottom: 10px; }
71.search-input {
72  flex: 1;
73  background: rgba(0,180,216,0.1);
74  border: 2px solid #00b4d8;
75  color: #00ff88;
76  padding: 10px 14px;
77  border-radius: 8px;
78  font-family: monospace;
79  font-size: 14px;
80  outline: none;
81}
82.search-input:focus { border-color: #00ff88; }
83.btn {
84  background: #00b4d8;
85  color: white;
86  border: none;
87  padding: 10px 16px;
88  border-radius: 8px;
89  cursor: pointer;
90}
91.query-info { color: #00b4d8; font-size: 13px; }
92</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. Pourquoi définit-on un template ref comme ref<HTMLInputElement | null>(null) ?

  2. 2. Qu'apporte InjectionKey<T> dans le système provide/inject ?

Tâches pratiques dans le jeu

  • Tri vertical

    Ordonne les étapes pour travailler avec un template ref typé :

  • Éditeur de code

    Définis une InjectionKey, utilise provide dans le parent et inject dans l'enfant.

  • Clique dans l'ordre

    Ordonne les étapes pour créer un provide/inject typé :

  • Tri horizontal

    Ordonne la syntaxe d'une référence de composant typée :