Kurs Vue.js · Moduł 3: Reaktywność
Debugowanie Reaktywności
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)) // falseRef 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.33Dł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ętrznychJSON.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 VueisReactive(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. Co zwraca isRef()?
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().