Kurs Vue.js · Moduł 3: Reaktywność
Projekt: Reaktywny Panel Kontrolny NOVA LAB
W tej lekcji2
Finalny projekt modułu Reaktora Energetycznego! Połącz wszystkie techniki reaktywności Vue.js w jednym systemie monitorowania. Zbudujesz kompletny dashboard reaktora plazmowego, który w czasie rzeczywistym śledzi parametry, generuje alarmy i optymalizuje zużycie zasobów.
Wyobraź sobie nocną zmianę w sterowni: temperatura rdzenia rośnie, ciśnienie spada, a dyżurny inżynier ma tylko panel, który odświeża się wtedy, gdy ktoś sobie o tym przypomni. W tym projekcie budujesz panel, który nie potrzebuje przypominania - każdy odczyt, alarm i wpis historii trafia na ekran, bo tak ułożysz dane.
Podsumowanie modułu
W tym module poznałeś:
- ref() - reaktywne zmienne prymitywne. Dostęp przez
.valuew skryptach, automatyczny unwrap w template. Idealne dla pojedynczych wartości jak temperatura, status, licznik. - reactive() - reaktywne obiekty i tablice. Bezpośredni dostęp do właściwości bez
.value. Nie można zastąpić całego obiektu (tylko właściwości). Nie destructure bez toRefs! - Głęboka vs płytka reaktywność -
shallowRefśledzi tylko podmianę.value, ashallowReactivetylko pierwszy poziom właściwości.triggerRefwymusza aktualizację po zmianie w miejscu.markRawcałkowicie wyłącza reaktywność. Przydatne dla dużych danych z API i bibliotek zewnętrznych. - toRef / toRefs - bezpieczna destrukturyzacja obiektów reaktywnych z zachowaniem reaktywności.
toRef(obj, 'key')dla pojedynczej właściwości,toRefs(obj)dla wszystkich. - Narzędzia debugowania -
isRef(),isReactive(),unref(),toRaw()do sprawdzania i rozpakowywania reaktywnych wartości. - Praktyczne wzorce - panele z walidacją, systemy filtrowania, koszyki zakupów z computed getters oraz
readonly()do udostępniania stanu bez prawa zapisu.
Wymagania projektu
Zbuduj System Monitorowania Reaktora który zawiera:
1. Stan reaktora z reactive()
Stwórz obiekt reaktywny przechowujący dane reaktora: temperaturę (Kelvin), ciśnienie (kPa), poziom energii (%), status (ACTIVE/STANDBY/CRITICAL). Dodaj przyciski do modyfikowania wartości.
Starter trzyma temperaturę w stopniach Celsjusza i używa progów 400 i 500 - przestaw go na kelwiny i progi z wymagań poniżej. Przyciski niech wołają funkcje, które zmieniają właściwości obiektu. Samego obiektu nigdy nie podmieniaj, bo szablon straciłby z nim połączenie.
2. Alarmy z ref()
Użyj ref([]) do przechowywania listy alarmów systemowych. Gdy temperatura przekroczy 4000K lub ciśnienie spadnie poniżej 50kPa, automatycznie dodaj alarm do listy. Wyświetl alerty z możliwością usuwania.
Sprawdzaj progi w tych samych funkcjach, które zmieniają odczyty - tak robi już funkcja checkAlarms w starterze. Nadaj każdemu alarmowi własny identyfikator i użyj go jako :key zamiast indeksu, bo nowe alarmy trafiają na początek listy. Przed dodaniem sprawdź metodą some(), czy taki alarm już wisi, żeby każde kliknięcie nie dokładało duplikatu.
3. Bezpieczna destrukturyzacja z toRefs
Użyj toRefs aby wyodrębnić poszczególne wartości z obiektu reaktora. Wyświetl je w osobnych komponentach/sekcjach interfejsu bez utraty reaktywności.
Zmiana przez taki ref, na przykład temperature.value += 100, zmienia też reactor.temperature - to ten sam przewód, podłączony z dwóch stron.
4. Optymalizacja z markRaw
Stwórz tablicę historycznych odczytów i oznacz ją jako markRaw, aby nie obciążać systemu reaktywności. Wyświetl ostatnie 10 odczytów w panelu historii.
Tu czai się pułapka: tablicy oznaczonej markRaw Vue w ogóle nie obserwuje, więc samo push() nie odświeży panelu historii. Trzymaj ją w shallowRef i po dopisaniu odczytu wywołaj triggerRef, dokładnie jak w lekcji o płytkiej reaktywności:
1const history = shallowRef(markRaw([]))
2
3function recordReading(reading) {
4 history.value.push(markRaw(reading))
5 triggerRef(history)
6}Każdy odczyt zostaje zwykłym obiektem bez Proxy, a panel odświeża się raz na wpis. Ostatnie dziesięć pozycji pokażesz przez history.slice(-10) w szablonie, gdzie ref jest już rozpakowany.
Polecam zbudować projekt warstwami: najpierw dane, potem akcje, na końcu szablon, i sprawdzać każdą warstwę w Vue DevTools. W module 4, w Centrum Obliczeń, zastąpisz ręczne sprawdzanie progów właściwościami computed i watcherami.
Rozbuduj starter poniżej!
Zapamiętaj: panel kontrolny jest tak wiarygodny jak jego dane - trzymaj je w jednym miejscu, zmieniaj tylko akcjami i wybieraj reaktywność świadomie.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref, reactive, toRefs, markRaw } from 'vue'
3
4// System Monitorowania Reaktora - Projekt koncowy modulu 3
5const reactor = reactive({
6 name: 'Reaktor Alpha',
7 temperature: 350,
8 pressure: 101,
9 energy: 85,
10 status: 'NOMINAL'
11})
12
13const { temperature, pressure, energy, status } = toRefs(reactor)
14
15const alarms = ref([])
16
17// Logi historyczne - nie potrzebuja reaktywnosci
18const historicalLogs = markRaw([
19 { time: '08:00', event: 'System start' },
20 { time: '08:15', event: 'Reactor online' },
21 { time: '08:30', event: 'All systems nominal' }
22])
23
24function adjustTemperature(delta) {
25 reactor.temperature += delta
26 checkAlarms()
27}
28
29function adjustPressure(delta) {
30 reactor.pressure += delta
31 checkAlarms()
32}
33
34function checkAlarms() {
35 if (reactor.temperature > 500) {
36 alarms.value.unshift('KRYTYCZNE: Temperatura przekroczona!')
37 reactor.status = 'CRITICAL'
38 } else if (reactor.temperature > 400) {
39 alarms.value.unshift('OSTRZEZENIE: Wysoka temperatura')
40 reactor.status = 'WARNING'
41 } else {
42 reactor.status = 'NOMINAL'
43 }
44}
45
46// TODO: Dodaj wiecej parametrow reaktora
47// TODO: Dodaj automatyczne alarmy dla cisnienia
48// TODO: Rozbuduj system logow
49</script>
50
51<template>
52 <div class="reactor-monitor">
53 <h1>System Monitorowania Reaktora</h1>
54 <div class="status-bar" :class="status.toLowerCase()">
55 Status: {{ status }}
56 </div>
57
58 <div class="panels">
59 <div class="panel">
60 <h3>Temperatura</h3>
61 <p class="value">{{ temperature }}°C</p>
62 <button @click="adjustTemperature(-10)">-10</button>
63 <button @click="adjustTemperature(10)">+10</button>
64 </div>
65 <div class="panel">
66 <h3>Cisnienie</h3>
67 <p class="value">{{ pressure }} kPa</p>
68 <button @click="adjustPressure(-5)">-5</button>
69 <button @click="adjustPressure(5)">+5</button>
70 </div>
71 <div class="panel">
72 <h3>Energia</h3>
73 <p class="value">{{ energy }}%</p>
74 </div>
75 </div>
76
77 <div class="alarms" v-if="alarms.length">
78 <h3>Alarmy</h3>
79 <p v-for="(alarm, i) in alarms" :key="i">{{ alarm }}</p>
80 </div>
81 </div>
82</template>
83
84<style scoped>
85.reactor-monitor { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
86h1 { color: #ff6b35; text-align: center; }
87.status-bar { text-align: center; padding: 0.5rem; border-radius: 4px; font-weight: bold; margin: 1rem 0; }
88.status-bar.nominal { background: #1a4a1a; color: #00ff88; }
89.status-bar.warning { background: #4a4a1a; color: #ffaa00; }
90.status-bar.critical { background: #4a1a1a; color: #ff4444; }
91.panels { display: flex; gap: 1rem; flex-wrap: wrap; }
92.panel { background: #1a1a4e; padding: 1rem; border-radius: 8px; flex: 1; min-width: 150px; text-align: center; }
93.value { font-size: 2rem; color: #00d4ff; margin: 0.5rem 0; }
94button { background: #333; color: white; border: none; padding: 0.5rem 1rem; margin: 0.25rem; cursor: pointer; border-radius: 4px; }
95button:hover { background: #555; }
96.alarms { margin-top: 1rem; background: #2a1a1a; padding: 1rem; border-radius: 8px; }
97.alarms h3 { color: #ff4444; }
98.alarms p { color: #ffaa00; font-size: 0.9rem; }
99</style>Widzisz błąd w tej lekcji?