Kurs Vue.js · Moduł 11: Pinia
Podstawy Pinia
W tej lekcji5
Witaj w Centralnej Bazie Danych NOVA LAB! Rok 2087 - tu przechowujemy wszystkie krytyczne dane misji marsjańskiej. Podobnie jak główny komputer bazy przechowuje informacje o systemach, załodze i zasobach, Pinia przechowuje globalny stan aplikacji Vue.
Czym jest Pinia?
Pinia to oficjalna biblioteka state management dla Vue 3. Jest następcą Vuex i oferuje znacznie prostszą składnię. Wyobraź sobie ją jako centralny komputer NOVA LAB - jeden punkt kontroli, z którego wszystkie moduły stacji pobierają dane.
Główne zalety Pinia:
- Prostsza składnia niż Vuex (brak mutations!)
- Pełne wsparcie TypeScript
- Świetna integracja z Vue DevTools
- Modułowa architektura - wiele niezależnych stores
Instalacja i konfiguracja
Aby uruchomić Pinia, potrzebujemy dwóch kroków - zainstalować pakiet i podłączyć go do aplikacji:
1npm install pinia1// main.js
2import { createApp } from 'vue'
3import { createPinia } from 'pinia'
4import App from './App.vue'
5
6const pinia = createPinia()
7const app = createApp(App)
8
9app.use(pinia)
10app.mount('#app')Funkcja createPinia() tworzy instancję Pinia, a app.use(pinia) rejestruje ją jako plugin Vue. Od tego momentu wszystkie stores są dostępne w całej aplikacji.
Definiowanie Store - defineStore
Store tworzymy za pomocą defineStore. Przyjmuje dwa argumenty: unikalną nazwę store i funkcję setup (Composition API style):
1// stores/mission.js
2import { defineStore } from 'pinia'
3import { ref, computed } from 'vue'
4
5export const useMissionStore = defineStore('mission', () => {
6 // State - dane reaktywne
7 const missionName = ref('Mars Exploration Alpha')
8 const status = ref('active')
9 const crewCount = ref(6)
10
11 // Getters - wartości wyliczane
12 const statusLabel = computed(() =>
13 status.value === 'active' ? 'AKTYWNA' : 'NIEAKTYWNA'
14 )
15
16 // Actions - funkcje modyfikujące state
17 function updateStatus(newStatus) {
18 status.value = newStatus
19 }
20
21 // Zwracamy wszystko co ma być dostępne
22 return {
23 missionName,
24 status,
25 crewCount,
26 statusLabel,
27 updateStatus
28 }
29})Konwencja nazewnictwa: useXxxStore - prefix use i suffix Store.
Użycie store w komponencie
1<script setup>
2import { useMissionStore } from '@/stores/mission'
3
4const missionStore = useMissionStore()
5
6// Dostęp do state
7console.log(missionStore.missionName)
8
9// Wywołanie action
10missionStore.updateStatus('completed')
11</script>
12
13<template>
14 <div>
15 <h1>{{ missionStore.missionName }}</h1>
16 <p>Status: {{ missionStore.statusLabel }}</p>
17 <button @click="missionStore.updateStatus('paused')">
18 Wstrzymaj misję
19 </button>
20 </div>
21</template>storeToRefs - reaktywna destrukturyzacja
Zwykła destrukturyzacja traci reaktywność. Użyj storeToRefs dla state i getters:
1<script setup>
2import { useMissionStore } from '@/stores/mission'
3import { storeToRefs } from 'pinia'
4
5const store = useMissionStore()
6
7// State i getters - storeToRefs (zachowuje reaktywność)
8const { missionName, status, statusLabel } = storeToRefs(store)
9
10// Actions - normalna destrukturyzacja (to funkcje, nie refs)
11const { updateStatus } = store
12</script>Kod do tej lekcji: App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4// Symulacja store Pinia (w stylu Composition API)
5// W prawdziwej aplikacji: import { defineStore } from 'pinia'
6
7// State
8const missionName = ref('Eksploracja Marsa Alfa')
9const status = ref('active')
10const crewCount = ref(6)
11const fuelLevel = ref(87)
12
13// Getters
14const statusLabel = computed(() =>
15 status.value === 'active' ? 'AKTYWNA' : 'NIEAKTYWNA'
16)
17
18const fuelStatus = computed(() => {
19 if (fuelLevel.value > 70) return 'Optimal'
20 if (fuelLevel.value > 30) return 'Moderate'
21 return 'Critical'
22})
23
24// Actions
25function toggleStatus() {
26 status.value = status.value === 'active' ? 'paused' : 'active'
27}
28
29function consumeFuel(amount) {
30 fuelLevel.value = Math.max(0, fuelLevel.value - amount)
31}
32
33function refuel() {
34 fuelLevel.value = 100
35}
36</script>
37
38<template>
39 <div class="nova-lab">
40 <h1>NOVA LAB - podstawy Pinia</h1>
41
42 <div class="panel">
43 <h2>Store misji</h2>
44 <div class="stat">
45 <span class="label">Misja:</span>
46 <span>{{ missionName }}</span>
47 </div>
48 <div class="stat">
49 <span class="label">Status:</span>
50 <span :class="status">{{ statusLabel }}</span>
51 </div>
52 <div class="stat">
53 <span class="label">Załoga:</span>
54 <span>{{ crewCount }} osób</span>
55 </div>
56 <div class="stat">
57 <span class="label">Paliwo:</span>
58 <div class="fuel-bar">
59 <div class="fuel-fill" :style="{ width: fuelLevel + '%' }" :class="fuelStatus.toLowerCase()"></div>
60 </div>
61 <span>{{ fuelLevel }}% ({{ fuelStatus }})</span>
62 </div>
63
64 <div class="actions">
65 <button @click="toggleStatus" class="btn">
66 {{ status === 'active' ? 'Wstrzymaj' : 'Wznów' }}
67 </button>
68 <button @click="consumeFuel(10)" class="btn btn-warn">
69 Zużyj paliwo (-10)
70 </button>
71 <button @click="refuel" class="btn btn-success">
72 Zatankuj
73 </button>
74 </div>
75 </div>
76 </div>
77</template>
78
79<style scoped>
80.nova-lab {
81 background: #0a0e27;
82 color: #00ff88;
83 min-height: 100vh;
84 padding: 20px;
85 font-family: monospace;
86}
87h1 { color: #00ff88; margin-bottom: 20px; text-align: center; }
88.panel {
89 background: rgba(0, 180, 216, 0.1);
90 border: 2px solid #00b4d8;
91 padding: 20px;
92 max-width: 500px;
93 margin: 0 auto;
94}
95h2 { color: #00b4d8; margin-bottom: 15px; }
96.stat {
97 display: flex;
98 align-items: center;
99 gap: 10px;
100 margin: 10px 0;
101 flex-wrap: wrap;
102}
103.label { color: #00b4d8; min-width: 80px; }
104.active { color: #00ff88; font-weight: bold; }
105.paused { color: #ff9900; font-weight: bold; }
106.fuel-bar {
107 width: 150px;
108 height: 16px;
109 background: #1a1e3f;
110 border: 1px solid #00b4d8;
111}
112.fuel-fill {
113 height: 100%;
114 transition: width 0.3s;
115}
116.fuel-fill.optimal { background: #00ff88; }
117.fuel-fill.moderate { background: #ff9900; }
118.fuel-fill.critical { background: #ff4444; }
119.actions {
120 display: flex;
121 gap: 8px;
122 margin-top: 15px;
123 flex-wrap: wrap;
124}
125.btn {
126 background: #00b4d8;
127 color: #0a0e27;
128 border: none;
129 padding: 8px 16px;
130 cursor: pointer;
131 font-family: monospace;
132 font-weight: bold;
133}
134.btn:hover { background: #00ff88; }
135.btn-warn { background: #ff9900; }
136.btn-warn:hover { background: #ffbb33; }
137.btn-success { background: #00ff88; }
138.btn-success:hover { background: #33ffaa; }
139</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. Czym jest Pinia w ekosystemie Vue 3?
2. Jak utworzyć instancję Pinia w main.js?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Stwórz stan reaktywny (missionName, status, crewCount) i akcję toggleStatus.
- Klikanie w kolejności
Ułóż kod definiowania store o nazwie 'mission':
- Edytor kodu
Użyj storeToRefs do destrukturyzacji state i wywołaj action z template.