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

Projekt: Reaktywny Panel Kontrolny NOVA LAB

3 min czytania
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 .value w 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, a shallowReactive tylko pierwszy poziom właściwości. triggerRef wymusza aktualizację po zmianie w miejscu. markRaw cał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?

Przydatne artykuły