Kurs Vue.js · Moduł 3: Reaktywność

Debugowanie Reaktywności

4 min czytania
W tej lekcji6

Nawet najlepsi inżynierowie NOVA LAB muszą czasem debugować swoje systemy. Poznaj narzędzia do debugowania reaktywności.

"Panel się nie odświeża" to najczęstsze zgłoszenie w dzienniku usterek stacji. Przyczyna prawie zawsze leży w danych: wartość okazuje się zwykłym obiektem zamiast Proxy albo straciła połączenie po destrukturyzacji. Zanim zaczniesz przepisywać komponent, sprawdź, z czym naprawdę pracujesz - Vue ma do tego zestaw testerów diagnostycznych.

isRef() i isReactive()

isRef() zwraca true, gdy wartość jest refem. isReactive() sprawdza, czy obiekt jest Proxy utworzonym przez reactive() lub shallowReactive(). Obie funkcje zwracają wartość logiczną:

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

Ref nie jest Proxy, dlatego isReactive(powerLevel) daje false. Obiekt schowany w refie już nim jest: isReactive(ref({}).value) zwróci true. Pokrewne testery to isProxy(), który rozpoznaje każde Proxy Vue, także tylko do odczytu, oraz isReadonly().

unref() - Bezpieczne Rozpakowanie Odczytu

unref(x) to skrót od isRef(x) ? x.value : x. Pozwala napisać funkcję, która przyjmie zarówno ref, jak i zwykłą wartość:

1import { ref, unref } from 'vue'
2
3const temperature = ref(2500)
4const plainReading = 1500
5
6// unref zwraca .value dla ref, lub wartość bezpośrednio
7console.log(unref(temperature))      // 2500
8console.log(unref(plainReading)) // 1500
9
10// Przydatne w funkcjach uniwersalnych
11function convertToFahrenheit(kelvin) {
12  return (unref(kelvin) - 273.15) * 9/5 + 32
13}
14
15convertToFahrenheit(temperature)      // 4040.3299999999995 (ok. 4040.33)
16convertToFahrenheit(plainReading) // 2240.33

Długi ogon po przecinku to zwykła arytmetyka zmiennoprzecinkowa JavaScriptu, nie wina Vue - do wyświetlenia użyj toFixed(2). Od Vue 3.3 istnieje też toValue(), które działa jak unref(), a dodatkowo wywołuje przekazane gettery.

toRaw() - Surowy Obiekt Sensora

toRaw() zwraca oryginalny obiekt schowany za Proxy. To furtka awaryjna: odczyt przez nią nie jest śledzony, a zapis nie odświeży widoku.

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) // Oryginalny obiekt bez Proxy
10
11// Przydatne do:
12// - Porównywania obiektów
13// - Przekazywania do API, które nie przyjmują Proxy (np. structuredClone)
14// - Bibliotek zewnętrznych

JSON.stringify() radzi sobie z Proxy bez problemu, ale structuredClone() czy postMessage() rzucą błąd DataCloneError - wtedy podaj im toRaw(reactor). Dokumentacja odradza trzymanie trwałej referencji do surowego obiektu, bo łatwo o zmiany, których panel nie zauważy.

markRaw() - Wyłączenie Reaktywności

markRaw() oznacza obiekt tak, że Vue nigdy nie zamieni go w Proxy, i zwraca ten sam obiekt:

1import { reactive, markRaw } from 'vue'
2
3const externalSensorLibrary = markRaw({
4  hardwareInterface: new Map(),
5  calibrate() { /* ... */ }
6})
7
8const state = reactive({
9  sensor: externalSensorLibrary // Nie będzie reaktywny
10})
11
12// Przydatne dla:
13// - Bibliotek sprzętowych (sensor drivers, hardware interfaces)
14// - Dużych niezmiennych danych z bazy NOVA LAB
15// - Obiektów komponentów Vue

isReactive(state.sensor) zwróci false, a state.sensor to dokładnie ten sam obiekt co externalSensorLibrary. Proxy potrafi wręcz zepsuć niektóre klasy z bibliotek: metoda, która sięga do prywatnego pola zapisanego z krzyżykiem, na przykład #points, rzuci wtedy TypeError. markRaw temu zapobiega. Uwaga: oznaczenie dotyczy tylko samego obiektu, nie jego zagnieżdżonych pól.

Debugging z watch

watch(źródło, callback) wywołuje funkcję z nową i starą wartością po każdej zmianie źródła, a opcja immediate: true uruchamia ją od razu. Watchery omówimy w module 4, ale już teraz pomogą w śledztwie:

1import { ref, watch } from 'vue'
2
3const count = ref(0)
4
5watch(count, (newValue, oldValue) => {
6  console.log(`count changed: ${oldValue} → ${newValue}`)
7  console.trace() // Stack trace - skąd przyszła zmiana?
8}, { immediate: true })

Pierwszy wpis to count changed: undefined → 0, bo przy starcie nie ma jeszcze starej wartości. console.trace() wypisuje stos wywołań, więc widzisz, która funkcja zmieniła odczyt. W trybie deweloperskim Vue oferuje też opcje onTrack i onTrigger dla watcherów i computed oraz haki onRenderTracked i onRenderTriggered.

Vue DevTools

Vue DevTools to rozszerzenie przeglądarki, dostępne też jako wtyczka do Vite. W Vue DevTools możesz:

  • Zobaczyć wszystkie reaktywne dane
  • Śledzić zmiany w czasie rzeczywistym
  • Edytować wartości na żywo
  • Zobaczyć drzewo zależności

Zakładka komponentów pokazuje drzewo komponentów z ich stanem, Timeline zapisuje zdarzenia i czasy renderowania, a zakładka Graph pokazuje reaktywne zależności wybranego komponentu. Zainstaluj je pierwszego dnia - oszczędzą Ci godzin zgadywania. W następnej lekcji złożysz wszystkie poznane narzędzia w praktyczne panele.

Zapamiętaj: zanim rozkręcisz reaktor, sprawdź go testerem - isRef i isReactive powiedzą Ci, czy przewód w ogóle jest podłączony.

Kod do tej lekcji: App.vue
1<script setup>
2import { ref, reactive, shallowRef, shallowReactive, readonly, triggerRef } from 'vue'
3
4// Zaawansowana reaktywność - shallow i readonly
5// System ochrony danych misji NOVA LAB
6
7// shallowRef - reaktywność tylko na poziomie .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 - reaktywność tylko na pierwszym poziomie
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 - tylko do odczytu
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// Przykład normalnego ref dla porównania
45const normalMissionData = ref({
46  id: 'MISSION-2087',
47  crew: ['Chen', 'Rodriguez', 'Kim']
48})
49
50// Demonstracja shallowRef
51function modifyShallowRef() {
52  // To NIE zadziała - zmiana głęboko zagnieżdżonej właściwości
53  missionData.value.objectives.primary = 'Build greenhouse'
54  console.log("Modified nested property, but UI won't update automatically")
55
56  // Musimy ręcznie wywołać triggerRef
57  // triggerRef(missionData)
58}
59
60function replaceShallowRef() {
61  // To ZADZIAŁA - zastąpienie całego .value
62  missionData.value = {
63    ...missionData.value,
64    objectives: {
65      primary: 'Build greenhouse',
66      secondary: 'Test farming systems'
67    }
68  }
69}
70
71function forceUpdateShallowRef() {
72  // Modyfikacja zagnieżdżonej właściwości
73  missionData.value.crew.push('Jackson')
74  // Ręczne wymuszenie aktualizacji
75  triggerRef(missionData)
76}
77
78// Demonstracja shallowReactive
79function modifyShallowReactive() {
80  // To ZADZIAŁA - zmiana na pierwszym poziomie
81  missionStatus.phase = 'OPERATIONAL'
82}
83
84function modifyDeepShallowReactive() {
85  // To NIE ZADZIAŁA automatycznie - zmiana głębiej
86  missionStatus.modules.habitation.progress = 100
87  console.log("Modified deep property, UI won't update")
88}
89
90function replaceNestedShallowReactive() {
91  // To ZADZIAŁA - zastąpienie obiektu na pierwszym poziomie
92  missionStatus.modules = {
93    ...missionStatus.modules,
94    habitation: { status: 'COMPLETE', progress: 100 }
95  }
96}
97
98// Demonstracja readonly
99function tryModifyReadonly() {
100  try {
101    // To NIE ZADZIAŁA - readonly blokuje modyfikacje
102    criticalParameters.maxCrewSize = 20
103  } catch (e) {
104    console.warn('Cannot modify readonly object')
105  }
106}
107
108// Liczniki dla demonstracji
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>SYSTEM OCHRONY DANYCH - NOVA LAB</h1>
118      <p class="subtitle">shallowRef(), shallowReactive(), readonly(), triggerRef()</p>
119    </div>
120
121    <!-- shallowRef Demo -->
122    <div class="demo-section">
123      <h2>shallowRef() - Płytka reaktywność</h2>
124      <div class="demo-grid">
125        <div class="info-card">
126          <h3>Charakterystyka</h3>
127          <ul>
128            <li>Reaktywne tylko na poziomie <code>.value</code></li>
129            <li>Zmiany głęboko zagnieżdżonych właściwości NIE wywołują aktualizacji</li>
130            <li>Użyj <code>triggerRef()</code> aby wymusić aktualizację</li>
131            <li>Wydajniejsze dla dużych obiektów</li>
132          </ul>
133        </div>
134
135        <div class="data-card">
136          <h3>Dane misji (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              Modyfikuj zagnieżdżone (nie zadziała)
142            </button>
143            <button @click="replaceShallowRef(); shallowRefCounter++" class="btn btn-success">
144              Zastąp .value (zadziała)
145            </button>
146            <button @click="forceUpdateShallowRef(); shallowRefCounter++" class="btn btn-info">
147              Wymuś aktualizację (triggerRef)
148            </button>
149          </div>
150
151          <div class="counter">
152            Liczba akcji: {{ shallowRefCounter }}
153          </div>
154        </div>
155      </div>
156
157      <div class="code-example">
158        <h4>Przykład kodu:</h4>
159        <pre>const data = shallowRef({ nested: { value: 1 } })
160
161// Nie wywoła aktualizacji UI
162data.value.nested.value = 2
163
164// Wywoła aktualizację UI
165data.value = { nested: { value: 2 } }
166
167// Wymuś aktualizację po modyfikacji
168data.value.nested.value = 2
169triggerRef(data)</pre>
170      </div>
171    </div>
172
173    <!-- shallowReactive Demo -->
174    <div class="demo-section">
175      <h2>shallowReactive() - Płytka reaktywność obiektów</h2>
176      <div class="demo-grid">
177        <div class="info-card">
178          <h3>Charakterystyka</h3>
179          <ul>
180            <li>Reaktywne tylko właściwości pierwszego poziomu</li>
181            <li>Zagnieżdżone obiekty NIE są reaktywne</li>
182            <li>Trzeba zastępować zagnieżdżone obiekty całkowicie</li>
183            <li>Wydajne dla płaskich struktur</li>
184          </ul>
185        </div>
186
187        <div class="data-card">
188          <h3>Status misji (shallowReactive)</h3>
189          <div class="status-display">
190            <p><strong>Nazwa:</strong> {{ missionStatus.name }}</p>
191            <p><strong>Faza:</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              Zmień fazę (1 poziom)
216            </button>
217            <button @click="modifyDeepShallowReactive(); shallowReactiveCounter++" class="btn btn-warning">
218              Zmień progress (głęboko)
219            </button>
220            <button @click="replaceNestedShallowReactive(); shallowReactiveCounter++" class="btn btn-success">
221              Zastąp moduł
222            </button>
223          </div>
224
225          <div class="counter">
226            Liczba akcji: {{ shallowReactiveCounter }}
227          </div>
228        </div>
229      </div>
230
231      <div class="code-example">
232        <h4>Przykład kodu:</h4>
233        <pre>const state = shallowReactive({
234  count: 0,
235  nested: { value: 1 }
236})
237
238// Zadziała - pierwszy poziom
239state.count = 1
240
241// Nie zadziała - zagnieżdżone
242state.nested.value = 2
243
244// Zadziała - zastąpienie
245state.nested = { value: 2 }</pre>
246      </div>
247    </div>
248
249    <!-- readonly Demo -->
250    <div class="demo-section">
251      <h2>readonly() - Dane tylko do odczytu</h2>
252      <div class="demo-grid">
253        <div class="info-card">
254          <h3>Charakterystyka</h3>
255          <ul>
256            <li>Całkowita ochrona przed modyfikacją</li>
257            <li>Idealny dla stałych konfiguracji</li>
258            <li>Zapobiega przypadkowym zmianom</li>
259            <li>Wyświetla ostrzeżenia w dev mode</li>
260          </ul>
261        </div>
262
263        <div class="data-card">
264          <h3>Parametry krytyczne (readonly)</h3>
265          <div class="critical-params">
266            <div class="param-item">
267              <span class="param-label">Max załoga:</span>
268              <span class="param-value">{{ criticalParameters.maxCrewSize }}</span>
269            </div>
270            <div class="param-item">
271              <span class="param-label">Min tlen (%):</span>
272              <span class="param-value">{{ criticalParameters.minOxygen }}</span>
273            </div>
274            <div class="param-item">
275              <span class="param-label">Max radiacja:</span>
276              <span class="param-value">{{ criticalParameters.maxRadiation }} mSv</span>
277            </div>
278            <div class="param-item">
279              <span class="param-label">Protokół awaryjny:</span>
280              <span class="param-value">{{ criticalParameters.safetyProtocols.emergency }}</span>
281            </div>
282            <div class="param-item">
283              <span class="param-label">System backup:</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              Próba modyfikacji (nie zadziała)
291            </button>
292          </div>
293
294          <div class="counter">
295            Nieudane próby modyfikacji: {{ readonlyCounter }}
296          </div>
297
298          <div class="warning-box">
299            Parametry krytyczne są zabezpieczone przed modyfikacją
300          </div>
301        </div>
302      </div>
303
304      <div class="code-example">
305        <h4>Przykład kodu:</h4>
306        <pre>const config = readonly(reactive({
307  apiKey: 'secret',
308  maxRetries: 3
309}))
310
311// Nie zadziała - readonly
312config.maxRetries = 5 // Warning w konsoli
313
314// Możemy tylko czytać
315console.log(config.maxRetries) // 3</pre>
316      </div>
317    </div>
318
319    <div class="info-box">
320      <h3>Kiedy używać zaawansowanych funkcji?</h3>
321      <div class="use-cases">
322        <div class="use-case">
323          <h4>shallowRef()</h4>
324          <p>Duże obiekty, gdzie zmieniasz cały obiekt naraz, nie pojedyncze właściwości</p>
325        </div>
326        <div class="use-case">
327          <h4>shallowReactive()</h4>
328          <p>Płaskie struktury danych z wieloma właściwościami na pierwszym poziomie</p>
329        </div>
330        <div class="use-case">
331          <h4>readonly()</h4>
332          <p>Konfiguracje, stałe, dane które nie powinny być modyfikowane</p>
333        </div>
334        <div class="use-case">
335          <h4>triggerRef()</h4>
336          <p>Manualne wymuszenie aktualizacji po modyfikacji 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>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co zwraca isRef()?

  2. 2. Co robi unref(value)?

To 2 z 5 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Użyj isRef() i isReactive() do sprawdzania typów reaktywnych wartości.

  • Klikanie w kolejności

    Ułóż kod sprawdzający typ wartości:

  • Edytor kodu

    Stwórz funkcję uniwersalną, która działa zarówno z ref jak i zwykłą wartością dzięki unref().

Przydatne artykuły