Kurs Vue.js · Moduł 11: Pinia
State w Pinia
W tej lekcji5
State to reaktywne dane przechowywane w store - jak baza danych głównego komputera NOVA LAB. Każdy odczyt sensora, każdy parametr misji jest tu zapisany i dostępny dla wszystkich modułów stacji.
Definiowanie state
W Composition API style state to zwykłe ref() i reactive():
1import { defineStore } from 'pinia'
2import { ref, reactive } from 'vue'
3
4export const useResourceStore = defineStore('resources', () => {
5 // Proste wartości - ref()
6 const oxygen = ref(98.5)
7 const water = ref(1250)
8 const energy = ref(87)
9 const isEmergency = ref(false)
10
11 // Obiekty - reactive() lub ref()
12 const crew = reactive({
13 commander: 'Nova',
14 engineer: 'Atlas',
15 scientist: 'Darwin'
16 })
17
18 // Tablice
19 const logs = ref([])
20
21 return { oxygen, water, energy, isEmergency, crew, logs }
22})Modyfikacja state
State można modyfikować bezpośrednio w actions lub z komponentu:
1export const useResourceStore = defineStore('resources', () => {
2 const oxygen = ref(98.5)
3
4 // Action modyfikuje state
5 function consumeOxygen(amount) {
6 oxygen.value = Math.max(0, oxygen.value - amount)
7 }
8
9 return { oxygen, consumeOxygen }
10})1<script setup>
2const store = useResourceStore()
3
4// Bezpośrednia modyfikacja (działa, ale actions są lepsze)
5store.oxygen = 95.0
6
7// Przez action (zalecane)
8store.consumeOxygen(3.5)
9</script>$patch - aktualizacja wielu właściwości
$patch pozwala zaktualizować wiele właściwości state jednocześnie - to bardziej wydajne niż modyfikacja po jednej:
1const store = useResourceStore()
2
3// Obiekt patch - proste nadpisanie
4store.$patch({
5 oxygen: 95.0,
6 water: 1200,
7 energy: 82,
8 isEmergency: false
9})
10
11// Funkcja patch - dla złożonych operacji
12store.$patch((state) => {
13 state.oxygen -= 3.5
14 state.logs.push({
15 type: 'consumption',
16 timestamp: Date.now()
17 })
18})$reset - przywracanie stanu początkowego
W Options API style $reset() działa automatycznie. W Composition API musisz zdefiniować własną funkcję reset:
1export const useResourceStore = defineStore('resources', () => {
2 const oxygen = ref(100)
3 const water = ref(1500)
4 const energy = ref(100)
5
6 // Custom reset dla Composition API
7 function $reset() {
8 oxygen.value = 100
9 water.value = 1500
10 energy.value = 100
11 }
12
13 return { oxygen, water, energy, $reset }
14})
15
16// Użycie
17const store = useResourceStore()
18store.$reset() // Przywraca wartości początkowe$state - dostęp do całego stanu
1const store = useResourceStore()
2
3// Odczyt całego state
4const snapshot = store.$state
5
6// Nadpisanie całego state
7store.$state = {
8 oxygen: 100,
9 water: 1500,
10 energy: 100,
11 isEmergency: false,
12 crew: { commander: 'Nova' },
13 logs: []
14}Kod do tej lekcji: App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4// Symulacja store zasobów
5const oxygen = ref(98.5)
6const water = ref(1250)
7const energy = ref(87)
8const logs = ref([])
9
10// Getters
11const oxygenStatus = computed(() => oxygen.value > 50 ? 'OK' : 'LOW')
12const waterStatus = computed(() => water.value > 500 ? 'OK' : 'LOW')
13const energyStatus = computed(() => energy.value > 40 ? 'OK' : 'LOW')
14
15// Actions
16function consumeOxygen(amount) {
17 oxygen.value = Math.max(0, +(oxygen.value - amount).toFixed(1))
18 addLog('Zużyto tlen: -' + amount + '%')
19}
20
21function consumeWater(amount) {
22 water.value = Math.max(0, water.value - amount)
23 addLog('Zużyto wodę: -' + amount + ' L')
24}
25
26function consumeEnergy(amount) {
27 energy.value = Math.max(0, energy.value - amount)
28 addLog('Zużyto energię: -' + amount + '%')
29}
30
31function addLog(message) {
32 logs.value.unshift({
33 message,
34 time: new Date().toLocaleTimeString()
35 })
36 if (logs.value.length > 8) logs.value.pop()
37}
38
39// Symulacja $patch
40function patchResources(updates) {
41 if (updates.oxygen !== undefined) oxygen.value = updates.oxygen
42 if (updates.water !== undefined) water.value = updates.water
43 if (updates.energy !== undefined) energy.value = updates.energy
44 addLog('Zasoby zaktualizowane przez $patch')
45}
46
47function resetResources() {
48 oxygen.value = 100
49 water.value = 1500
50 energy.value = 100
51 logs.value = []
52}
53</script>
54
55<template>
56 <div class="nova-lab">
57 <h1>NOVA LAB - zarządzanie stanem</h1>
58
59 <div class="grid">
60 <div class="resource-panel">
61 <h2>Zasoby</h2>
62 <div class="resource" v-for="r in [
63 { name: 'Tlen', value: oxygen, unit: '%', status: oxygenStatus, action: () => consumeOxygen(5) },
64 { name: 'Woda', value: water, unit: 'L', status: waterStatus, action: () => consumeWater(50) },
65 { name: 'Energia', value: energy, unit: '%', status: energyStatus, action: () => consumeEnergy(10) }
66 ]" :key="r.name">
67 <div class="r-header">
68 <span>{{ r.name }}</span>
69 <span :class="r.status.toLowerCase()">{{ r.status }}</span>
70 </div>
71 <div class="r-value">{{ r.value }}{{ r.unit }}</div>
72 <button @click="r.action" class="btn-sm">Zużyj</button>
73 </div>
74
75 <div class="btn-group">
76 <button @click="patchResources({ oxygen: 50, water: 800 })" class="btn">
77 $patch (O2=50, H2O=800)
78 </button>
79 <button @click="resetResources" class="btn btn-reset">
80 $reset
81 </button>
82 </div>
83 </div>
84
85 <div class="log-panel">
86 <h2>Dziennik aktywności</h2>
87 <div v-for="(log, i) in logs" :key="i" class="log-entry">
88 <span class="log-time">{{ log.time }}</span>
89 {{ log.message }}
90 </div>
91 <p v-if="!logs.length" class="empty">Brak aktywności...</p>
92 </div>
93 </div>
94 </div>
95</template>
96
97<style scoped>
98.nova-lab {
99 background: #0a0e27;
100 color: #00ff88;
101 min-height: 100vh;
102 padding: 20px;
103 font-family: monospace;
104}
105h1 { color: #00ff88; margin-bottom: 20px; text-align: center; }
106h2 { color: #00b4d8; margin-bottom: 15px; }
107.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; max-width: 700px; margin: 0 auto; }
108.resource-panel, .log-panel {
109 background: rgba(0, 180, 216, 0.1);
110 border: 2px solid #00b4d8;
111 padding: 15px;
112}
113.resource {
114 background: rgba(0, 255, 136, 0.05);
115 border: 1px solid #00ff88;
116 padding: 10px;
117 margin-bottom: 8px;
118}
119.r-header { display: flex; justify-content: space-between; margin-bottom: 5px; }
120.r-value { font-size: 1.2em; font-weight: bold; margin-bottom: 5px; }
121.ok { color: #00ff88; }
122.low { color: #ff4444; }
123.btn-sm {
124 background: #00b4d8;
125 color: #0a0e27;
126 border: none;
127 padding: 4px 12px;
128 cursor: pointer;
129 font-family: monospace;
130}
131.btn-group { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
132.btn {
133 background: #00b4d8;
134 color: #0a0e27;
135 border: none;
136 padding: 8px 12px;
137 cursor: pointer;
138 font-family: monospace;
139 font-size: 0.85em;
140}
141.btn:hover { background: #00ff88; }
142.btn-reset { background: #ff9900; }
143.log-entry {
144 padding: 5px 0;
145 border-bottom: 1px solid rgba(0, 180, 216, 0.3);
146 font-size: 0.85em;
147}
148.log-time { color: #00b4d8; margin-right: 8px; }
149.empty { color: #666; font-style: italic; }
150</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. Jak zdefiniować state w Composition API style?
2. Do czego służy $patch() w Pinia?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Stwórz store z ref() state, action consumeOxygen i action patchResources.
- Układanie w pionie
Ułóż kroki konfiguracji Pinia w main.js:
- Klikanie w kolejności
Ułóż kod destrukturyzacji store z zachowaniem reaktywności:
- Edytor kodu
Stwórz patchUpdate (ustawia wartości) i patchFunction (zmniejsza o 10%).