Cours Vue.js · Module 3: Réactivité

Déboguer la réactivité

5 min de lecture
Dans cette leçon6

Même les meilleurs ingénieurs du NOVA LAB doivent parfois déboguer leurs systèmes. Découvre les outils pour déboguer la réactivité.

« Le panneau ne se rafraîchit pas » est le signalement le plus fréquent du journal des pannes de la station. La cause se trouve presque toujours dans les données : la valeur s'avère être un objet ordinaire au lieu d'un Proxy, ou elle a perdu sa connexion après une déstructuration. Avant de réécrire le composant, vérifie avec quoi tu travailles vraiment - Vue a pour cela un jeu de testeurs de diagnostic.

isRef() et isReactive()

isRef() renvoie true quand la valeur est un ref. isReactive() vérifie si un objet est un Proxy créé par reactive() ou shallowReactive(). Les deux fonctions renvoient un booléen :

1import { ref, reactive, isRef, isReactive } from 'vue'
2
3const powerLevel = ref(0)
4const reactor = reactive({ name: 'Plasma Core Alpha' })
5const plainConfig = { mode: 'safe' }
6
7console.log(isRef(powerLevel))       // true
8console.log(isRef(reactor))     // false
9console.log(isReactive(reactor)) // true
10console.log(isReactive(powerLevel))   // false
11console.log(isReactive(plainConfig)) // false

Un ref n'est pas un Proxy, c'est pourquoi isReactive(powerLevel) donne false. En revanche, un objet rangé dans un ref en est un : isReactive(ref({}).value) renvoie true. Les testeurs voisins sont isProxy(), qui reconnaît tout Proxy de Vue, y compris en lecture seule, et isReadonly().

unref() - Extraction sûre d'une lecture

unref(x) est un raccourci pour isRef(x) ? x.value : x. Il permet d'écrire une fonction qui accepte aussi bien un ref qu'une valeur ordinaire :

1import { ref, unref } from 'vue'
2
3const temperature = ref(2500)
4const plainReading = 1500
5
6// unref renvoie .value pour un ref, ou la valeur directement
7console.log(unref(temperature))      // 2500
8console.log(unref(plainReading)) // 1500
9
10// Utile dans les fonctions universelles
11function convertToFahrenheit(kelvin) {
12  return (unref(kelvin) - 273.15) * 9/5 + 32
13}
14
15convertToFahrenheit(temperature)      // 4040.3299999999995 (env. 4040.33)
16convertToFahrenheit(plainReading) // 2240.33

La longue traîne après la virgule vient de l'arithmétique à virgule flottante de JavaScript, pas de Vue - pour l'affichage, utilise toFixed(2). Depuis Vue 3.3, il existe aussi toValue(), qui fonctionne comme unref() et appelle en plus les getters qu'on lui passe.

toRaw() - L'objet capteur brut

toRaw() renvoie l'objet original caché derrière le Proxy. C'est une échappatoire : les lectures par ce biais ne sont pas suivies, et les écritures ne rafraîchissent pas la vue.

1import { reactive, toRaw } from 'vue'
2
3const reactor = reactive({
4  name: 'Plasma Core Alpha',
5  temperature: 2500
6})
7
8const rawReactor = toRaw(reactor)
9console.log(rawReactor) // Objet original sans Proxy
10
11// Utile pour :
12// - Comparer des objets
13// - Passer des données à des API qui refusent un Proxy (ex. structuredClone)
14// - Les bibliothèques externes

JSON.stringify() gère un Proxy sans problème, mais structuredClone() ou postMessage() lèvent une DataCloneError - donne-leur plutôt toRaw(reactor). La documentation déconseille de garder une référence durable vers l'objet brut, car il est facile d'y faire des changements que le panneau ne remarquera jamais.

markRaw() - Désactiver la réactivité

markRaw() marque un objet pour que Vue ne le transforme jamais en Proxy, et renvoie ce même objet :

1import { reactive, markRaw } from 'vue'
2
3const externalSensorLibrary = markRaw({
4  hardwareInterface: new Map(),
5  calibrate() { /* ... */ }
6})
7
8const state = reactive({
9  sensor: externalSensorLibrary // Ne sera pas réactif
10})
11
12// Utile pour :
13// - Les bibliothèques matérielles (pilotes de capteurs, interfaces matérielles)
14// - Les gros ensembles de données immuables de la base de données du NOVA LAB
15// - Les objets de composants Vue

isReactive(state.sensor) renvoie false, et state.sensor est exactement le même objet que externalSensorLibrary. Un Proxy peut même casser certaines classes de bibliothèques : une méthode qui accède à un champ privé écrit avec un dièse, par exemple #points, lève alors une TypeError. markRaw l'évite. Attention : la marque ne concerne que l'objet lui-même, pas ses champs imbriqués.

Déboguer avec watch

watch(source, callback) appelle une fonction avec la nouvelle et l'ancienne valeur après chaque changement de la source, et l'option immediate: true la lance tout de suite. Nous verrons les watchers dans le module 4, mais ils peuvent déjà aider l'enquête :

1import { ref, watch } from 'vue'
2
3const count = ref(0)
4
5watch(count, (newValue, oldValue) => {
6  console.log(`count a changé : ${oldValue} → ${newValue}`)
7  console.trace() // Trace de pile - d'où vient le changement ?
8}, { immediate: true })

La première entrée est count a changé : undefined → 0, car au démarrage il n'y a pas encore d'ancienne valeur. console.trace() affiche la pile d'appels, donc tu vois quelle fonction a modifié la lecture. En mode développement, Vue propose aussi les options onTrack et onTrigger pour les watchers et les computed, ainsi que les hooks onRenderTracked et onRenderTriggered.

Vue DevTools

Vue DevTools est une extension de navigateur, disponible aussi sous forme de plugin Vite. Dans les Vue DevTools, tu peux :

  • Voir toutes les données réactives
  • Suivre les changements en temps réel
  • Éditer les valeurs en direct
  • Voir l'arbre des dépendances

L'onglet des composants montre l'arbre des composants avec leur état, la Timeline enregistre les événements et les temps de rendu, et l'onglet Graph montre les dépendances réactives du composant sélectionné. Installe-les dès le premier jour - elles t'éviteront des heures de devinettes. Dans la prochaine leçon, tu assembleras tous les outils appris en panneaux pratiques.

Retiens : avant de démonter le réacteur, vérifie-le au testeur - isRef et isReactive te disent si le câble est seulement branché.

Code de cette leçon : App.vue
1<script setup>
2import { ref, reactive, shallowRef, shallowReactive, readonly, triggerRef } from 'vue'
3
4// Réactivité avancée - shallow et readonly
5// Système de protection des données de mission NOVA LAB
6
7// shallowRef - réactivité uniquement au niveau .value
8const missionData = shallowRef({
9  id: 'MISSION-2087',
10  crew: ['Chen', 'Rodriguez', 'Kim'],
11  objectives: {
12    primary: 'Establish Mars base',
13    secondary: 'Collect samples'
14  }
15})
16
17// shallowReactive - réactivité uniquement au premier niveau
18const missionStatus = shallowReactive({
19  name: 'Mars Colony Alpha',
20  phase: 'DEPLOYMENT',
21  timeline: {
22    start: '2087-03-15',
23    current: '2087-06-20',
24    estimated_end: '2087-12-31'
25  },
26  modules: {
27    habitation: { status: 'ACTIVE', progress: 75 },
28    power: { status: 'ACTIVE', progress: 90 },
29    lifesupport: { status: 'TESTING', progress: 60 }
30  }
31})
32
33// readonly - lecture seule
34const criticalParameters = readonly(reactive({
35  maxCrewSize: 12,
36  minOxygen: 21,
37  maxRadiation: 100,
38  safetyProtocols: {
39    emergency: 'AUTO_SHUTDOWN',
40    backup: 'REDUNDANT_SYSTEMS'
41  }
42}))
43
44// Exemple de ref normal pour comparaison
45const normalMissionData = ref({
46  id: 'MISSION-2087',
47  crew: ['Chen', 'Rodriguez', 'Kim']
48})
49
50// Démonstration de shallowRef
51function modifyShallowRef() {
52  // Ceci NE marchera PAS - changement d'une propriété profondément imbriquée
53  missionData.value.objectives.primary = 'Build greenhouse'
54  console.log("Propriété imbriquée modifiée, mais l'interface ne se mettra pas à jour automatiquement")
55
56  // On doit appeler triggerRef manuellement
57  // triggerRef(missionData)
58}
59
60function replaceShallowRef() {
61  // Ceci marchera - on remplace le .value entier
62  missionData.value = {
63    ...missionData.value,
64    objectives: {
65      primary: 'Build greenhouse',
66      secondary: 'Test farming systems'
67    }
68  }
69}
70
71function forceUpdateShallowRef() {
72  // Modification d'une propriété imbriquée
73  missionData.value.crew.push('Jackson')
74  // On force manuellement la mise à jour
75  triggerRef(missionData)
76}
77
78// Démonstration de shallowReactive
79function modifyShallowReactive() {
80  // Ceci marchera - changement au premier niveau
81  missionStatus.phase = 'OPERATIONAL'
82}
83
84function modifyDeepShallowReactive() {
85  // Ceci NE marchera PAS automatiquement - changement plus profond
86  missionStatus.modules.habitation.progress = 100
87  console.log("Propriété profonde modifiée, l'interface ne se mettra pas à jour")
88}
89
90function replaceNestedShallowReactive() {
91  // Ceci marchera - on remplace l'objet au premier niveau
92  missionStatus.modules = {
93    ...missionStatus.modules,
94    habitation: { status: 'COMPLETE', progress: 100 }
95  }
96}
97
98// Démonstration de readonly
99function tryModifyReadonly() {
100  try {
101    // Ceci NE marchera PAS - readonly bloque les modifications
102    criticalParameters.maxCrewSize = 20
103  } catch (e) {
104    console.warn('Cannot modify readonly object')
105  }
106}
107
108// Compteurs pour la démonstration
109const shallowRefCounter = ref(0)
110const shallowReactiveCounter = ref(0)
111const readonlyCounter = ref(0)
112</script>
113
114<template>
115  <div class="advanced-reactivity">
116    <div class="header">
117      <h1>SYSTÈME DE PROTECTION DES DONNÉES - NOVA LAB</h1>
118      <p class="subtitle">shallowRef(), shallowReactive(), readonly(), triggerRef()</p>
119    </div>
120
121    <!-- Démo shallowRef -->
122    <div class="demo-section">
123      <h2>shallowRef() - Réactivité superficielle</h2>
124      <div class="demo-grid">
125        <div class="info-card">
126          <h3>Caractéristiques</h3>
127          <ul>
128            <li>Réactif uniquement au niveau <code>.value</code></li>
129            <li>Les changements de propriétés profondément imbriquées ne déclenchent PAS de mise à jour</li>
130            <li>Utilise <code>triggerRef()</code> pour forcer une mise à jour</li>
131            <li>Plus efficace pour les gros objets</li>
132          </ul>
133        </div>
134
135        <div class="data-card">
136          <h3>Données de mission (shallowRef)</h3>
137          <pre>{{ JSON.stringify(missionData, null, 2) }}</pre>
138
139          <div class="controls">
140            <button @click="modifyShallowRef(); shallowRefCounter++" class="btn btn-warning">
141              Modifier une imbriquée (ne marchera pas)
142            </button>
143            <button @click="replaceShallowRef(); shallowRefCounter++" class="btn btn-success">
144              Remplacer .value (marchera)
145            </button>
146            <button @click="forceUpdateShallowRef(); shallowRefCounter++" class="btn btn-info">
147              Forcer la mise à jour (triggerRef)
148            </button>
149          </div>
150
151          <div class="counter">
152            Nombre d'actions : {{ shallowRefCounter }}
153          </div>
154        </div>
155      </div>
156
157      <div class="code-example">
158        <h4>Exemple de code :</h4>
159        <pre>const data = shallowRef({ nested: { value: 1 } })
160
161// Ne déclenchera pas de mise à jour de l'interface
162data.value.nested.value = 2
163
164// Déclenchera une mise à jour de l'interface
165data.value = { nested: { value: 2 } }
166
167// Forcer la mise à jour après modification
168data.value.nested.value = 2
169triggerRef(data)</pre>
170      </div>
171    </div>
172
173    <!-- Démo shallowReactive -->
174    <div class="demo-section">
175      <h2>shallowReactive() - Réactivité superficielle pour les objets</h2>
176      <div class="demo-grid">
177        <div class="info-card">
178          <h3>Caractéristiques</h3>
179          <ul>
180            <li>Seules les propriétés de premier niveau sont réactives</li>
181            <li>Les objets imbriqués ne sont PAS réactifs</li>
182            <li>Les objets imbriqués doivent être remplacés entièrement</li>
183            <li>Efficace pour les structures plates</li>
184          </ul>
185        </div>
186
187        <div class="data-card">
188          <h3>Statut de la mission (shallowReactive)</h3>
189          <div class="status-display">
190            <p><strong>Nom :</strong> {{ missionStatus.name }}</p>
191            <p><strong>Phase :</strong>
192              <span class="phase-badge">{{ missionStatus.phase }}</span>
193            </p>
194            <p><strong>Timeline :</strong> {{ missionStatus.timeline.current }}</p>
195
196            <div class="modules-grid">
197              <div
198                v-for="(module, key) in missionStatus.modules"
199                :key="key"
200                class="module-card"
201              >
202                <div class="module-name">{{ key }}</div>
203                <div class="module-status">{{ module.status }}</div>
204                <div class="progress-bar">
205                  <div class="progress-fill" :style="{ width: module.progress + '%' }">
206                    {{ module.progress }}%
207                  </div>
208                </div>
209              </div>
210            </div>
211          </div>
212
213          <div class="controls">
214            <button @click="modifyShallowReactive(); shallowReactiveCounter++" class="btn btn-success">
215              Changer la phase (1er niveau)
216            </button>
217            <button @click="modifyDeepShallowReactive(); shallowReactiveCounter++" class="btn btn-warning">
218              Changer la progression (profond)
219            </button>
220            <button @click="replaceNestedShallowReactive(); shallowReactiveCounter++" class="btn btn-success">
221              Remplacer le module
222            </button>
223          </div>
224
225          <div class="counter">
226            Nombre d'actions : {{ shallowReactiveCounter }}
227          </div>
228        </div>
229      </div>
230
231      <div class="code-example">
232        <h4>Exemple de code :</h4>
233        <pre>const state = shallowReactive({
234  count: 0,
235  nested: { value: 1 }
236})
237
238// Marchera - premier niveau
239state.count = 1
240
241// Ne marchera pas - imbriqué
242state.nested.value = 2
243
244// Marchera - remplacement
245state.nested = { value: 2 }</pre>
246      </div>
247    </div>
248
249    <!-- Démo readonly -->
250    <div class="demo-section">
251      <h2>readonly() - Données en lecture seule</h2>
252      <div class="demo-grid">
253        <div class="info-card">
254          <h3>Caractéristiques</h3>
255          <ul>
256            <li>Protection complète contre la modification</li>
257            <li>Idéal pour les configurations constantes</li>
258            <li>Empêche les changements accidentels</li>
259            <li>Affiche des avertissements en mode dev</li>
260          </ul>
261        </div>
262
263        <div class="data-card">
264          <h3>Paramètres critiques (readonly)</h3>
265          <div class="critical-params">
266            <div class="param-item">
267              <span class="param-label">Équipage max :</span>
268              <span class="param-value">{{ criticalParameters.maxCrewSize }}</span>
269            </div>
270            <div class="param-item">
271              <span class="param-label">Oxygène min (%) :</span>
272              <span class="param-value">{{ criticalParameters.minOxygen }}</span>
273            </div>
274            <div class="param-item">
275              <span class="param-label">Radiation max :</span>
276              <span class="param-value">{{ criticalParameters.maxRadiation }} mSv</span>
277            </div>
278            <div class="param-item">
279              <span class="param-label">Protocole d'urgence :</span>
280              <span class="param-value">{{ criticalParameters.safetyProtocols.emergency }}</span>
281            </div>
282            <div class="param-item">
283              <span class="param-label">Système de secours :</span>
284              <span class="param-value">{{ criticalParameters.safetyProtocols.backup }}</span>
285            </div>
286          </div>
287
288          <div class="controls">
289            <button @click="tryModifyReadonly(); readonlyCounter++" class="btn btn-danger">
290              Tenter une modification (ne marchera pas)
291            </button>
292          </div>
293
294          <div class="counter">
295            Tentatives de modification échouées : {{ readonlyCounter }}
296          </div>
297
298          <div class="warning-box">
299            Les paramètres critiques sont protégés contre la modification
300          </div>
301        </div>
302      </div>
303
304      <div class="code-example">
305        <h4>Exemple de code :</h4>
306        <pre>const config = readonly(reactive({
307  apiKey: 'secret',
308  maxRetries: 3
309}))
310
311// Ne marchera pas - readonly
312config.maxRetries = 5 // Avertissement dans la console
313
314// On ne peut que lire
315console.log(config.maxRetries) // 3</pre>
316      </div>
317    </div>
318
319    <div class="info-box">
320      <h3>Quand utiliser les fonctions avancées ?</h3>
321      <div class="use-cases">
322        <div class="use-case">
323          <h4>shallowRef()</h4>
324          <p>Gros objets dont tu changes l'objet entier d'un coup, pas les propriétés individuelles</p>
325        </div>
326        <div class="use-case">
327          <h4>shallowReactive()</h4>
328          <p>Structures de données plates avec beaucoup de propriétés au premier niveau</p>
329        </div>
330        <div class="use-case">
331          <h4>readonly()</h4>
332          <p>Configurations, constantes, données qui ne doivent pas être modifiées</p>
333        </div>
334        <div class="use-case">
335          <h4>triggerRef()</h4>
336          <p>Forcer manuellement la mise à jour après une modification de shallowRef</p>
337        </div>
338      </div>
339    </div>
340  </div>
341</template>
342
343<style scoped>
344.advanced-reactivity {
345  min-height: 100vh;
346  background: #0a0e27;
347  color: #e0e0e0;
348  padding: 2rem;
349  font-family: 'Courier New', monospace;
350}
351
352.header {
353  text-align: center;
354  margin-bottom: 2rem;
355  border-bottom: 2px solid #00ff88;
356  padding-bottom: 1rem;
357}
358
359.header h1 {
360  color: #00ff88;
361  font-size: 2rem;
362  margin: 0;
363  text-shadow: 0 0 10px #00ff88;
364}
365
366.subtitle {
367  color: #00b4d8;
368  margin: 0.5rem 0 0 0;
369}
370
371.demo-section {
372  background: #1a1f3a;
373  padding: 1.5rem;
374  border-radius: 8px;
375  border: 1px solid #00b4d8;
376  margin-bottom: 2rem;
377}
378
379.demo-section h2 {
380  color: #00ff88;
381  margin-top: 0;
382}
383
384.demo-grid {
385  display: grid;
386  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
387  gap: 1.5rem;
388  margin-bottom: 1.5rem;
389}
390
391.info-card, .data-card {
392  background: #0a0e27;
393  padding: 1.5rem;
394  border-radius: 6px;
395}
396
397.info-card h3, .data-card h3 {
398  color: #00b4d8;
399  margin-top: 0;
400}
401
402.info-card ul {
403  list-style: none;
404  padding: 0;
405}
406
407.info-card li {
408  margin: 0.8rem 0;
409  padding-left: 1.5rem;
410  position: relative;
411}
412
413.info-card code {
414  background: #1a1f3a;
415  padding: 0.2rem 0.5rem;
416  border-radius: 3px;
417  color: #00ff88;
418}
419
420.data-card pre {
421  background: #1a1f3a;
422  padding: 1rem;
423  border-radius: 4px;
424  overflow-x: auto;
425  font-size: 0.85rem;
426  color: #00ff88;
427}
428
429.controls {
430  display: flex;
431  flex-direction: column;
432  gap: 0.8rem;
433  margin: 1rem 0;
434}
435
436.btn {
437  padding: 0.8rem 1rem;
438  font-size: 0.9rem;
439  border: none;
440  border-radius: 6px;
441  cursor: pointer;
442  font-weight: bold;
443  transition: all 0.3s ease;
444  font-family: 'Courier New', monospace;
445  text-align: left;
446}
447
448.btn-success {
449  background: linear-gradient(135deg, #00ff88, #00dd77);
450  color: #0a0e27;
451}
452
453.btn-warning {
454  background: linear-gradient(135deg, #ffaa00, #ff8800);
455  color: #0a0e27;
456}
457
458.btn-danger {
459  background: linear-gradient(135deg, #ff4444, #cc0000);
460  color: white;
461}
462
463.btn-info {
464  background: linear-gradient(135deg, #00b4d8, #0088cc);
465  color: white;
466}
467
468.btn:hover {
469  transform: scale(1.02);
470  box-shadow: 0 0 15px currentColor;
471}
472
473.counter {
474  background: #1a1f3a;
475  padding: 0.8rem;
476  border-radius: 4px;
477  text-align: center;
478  color: #00ff88;
479  font-weight: bold;
480  margin-top: 1rem;
481}
482
483.status-display p {
484  margin: 0.5rem 0;
485  color: #ccc;
486}
487
488.phase-badge {
489  background: #00ff88;
490  color: #0a0e27;
491  padding: 0.3rem 0.8rem;
492  border-radius: 4px;
493  font-weight: bold;
494}
495
496.modules-grid {
497  display: grid;
498  gap: 1rem;
499  margin-top: 1rem;
500}
501
502.module-card {
503  background: #1a1f3a;
504  padding: 1rem;
505  border-radius: 4px;
506}
507
508.module-name {
509  color: #00b4d8;
510  font-weight: bold;
511  text-transform: uppercase;
512}
513
514.module-status {
515  color: #00ff88;
516  font-size: 0.9rem;
517  margin: 0.3rem 0;
518}
519
520.progress-bar {
521  height: 24px;
522  background: #0a0e27;
523  border-radius: 4px;
524  overflow: hidden;
525  margin-top: 0.5rem;
526}
527
528.progress-fill {
529  height: 100%;
530  background: linear-gradient(90deg, #00b4d8, #00ff88);
531  display: flex;
532  align-items: center;
533  justify-content: center;
534  color: #0a0e27;
535  font-weight: bold;
536  transition: width 0.3s ease;
537}
538
539.critical-params {
540  background: #1a1f3a;
541  padding: 1rem;
542  border-radius: 4px;
543  border-left: 3px solid #ff4444;
544}
545
546.param-item {
547  display: flex;
548  justify-content: space-between;
549  padding: 0.5rem 0;
550  border-bottom: 1px solid #333;
551}
552
553.param-item:last-child {
554  border-bottom: none;
555}
556
557.param-label {
558  color: #888;
559}
560
561.param-value {
562  color: #00ff88;
563  font-weight: bold;
564}
565
566.warning-box {
567  background: #2a1f1a;
568  border: 1px solid #ff8800;
569  padding: 1rem;
570  border-radius: 4px;
571  color: #ffaa00;
572  margin-top: 1rem;
573  text-align: center;
574}
575
576.code-example {
577  background: #0a0e27;
578  padding: 1rem;
579  border-radius: 6px;
580  border-left: 3px solid #00ff88;
581}
582
583.code-example h4 {
584  color: #00b4d8;
585  margin-top: 0;
586}
587
588.code-example pre {
589  color: #00ff88;
590  font-size: 0.85rem;
591  overflow-x: auto;
592  margin: 0;
593}
594
595.info-box {
596  background: #1a1f3a;
597  padding: 1.5rem;
598  border-radius: 8px;
599  border-left: 4px solid #00ff88;
600}
601
602.info-box h3 {
603  color: #00ff88;
604  margin-top: 0;
605}
606
607.use-cases {
608  display: grid;
609  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
610  gap: 1rem;
611  margin-top: 1rem;
612}
613
614.use-case {
615  background: #0a0e27;
616  padding: 1rem;
617  border-radius: 4px;
618}
619
620.use-case h4 {
621  color: #00b4d8;
622  margin-top: 0;
623}
624
625.use-case p {
626  color: #ccc;
627  margin: 0;
628  font-size: 0.9rem;
629}
630</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. Que renvoie la fonction isRef() ?

  2. 2. Que fait unref(value) ?

Voici 2 des 5 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée des variables ref et reactive et utilise isRef() et isReactive() pour vérifier leurs types.

  • Clique dans l'ordre

    Range le code qui vérifie le type d'une valeur :

  • Éditeur de code

    Écris une fonction qui utilise unref() pour accepter à la fois des ref et des valeurs normales comme arguments.