Vue 3 Pinia vs Vuex: Nowoczesne zarządzanie stanem i pytania rekrutacyjne 2026
Porównanie Pinia i Vuex: architektura, TypeScript, Composition API, migracja, hydratacja SSR oraz najczęstsze pytania rekrutacyjne o zarządzanie stanem Vue na rok 2026.

Pinia kontra Vuex to najważniejsza zmiana w zarządzaniu stanem Vue od momentu premiery Vue 3. Pinia 3 jest oficjalnym rozwiązaniem rekomendowanym przez zespół Vue, a Vuex znajduje się w trybie maintenance. Zrozumienie różnic między tymi bibliotekami jest niezbędne dla każdego programisty Vue i stanowi częsty temat rozmów kwalifikacyjnych w 2026 roku.
Pinia to oficjalne rozwiązanie do zarządzania stanem w Vue 3. Vuex 5 został anulowany, a Evan You określił Pinię jako de facto Vuex 5. Wszystkie nowe projekty Vue 3 powinny korzystać z Pinia 3.
Pinia vs Vuex: Fundamentalne różnice architektoniczne
Podstawowa różnica architektoniczna między Pinią a Vuex polega na sposobie, w jaki dochodzi do mutacji stanu. Vuex wymusza ścisły jednokierunkowy przepływ danych: komponenty dispatczują akcje, akcje commitują mutacje, a mutacje modyfikują stan. Pinia eliminuje warstwę mutacji w całości, umożliwiając bezpośrednią modyfikację stanu z poziomu akcji lub nawet komponentów.
To uproszczenie redukuje boilerplate o około 40% w większości baz kodu. Tam, gdzie Vuex wymaga czterech konceptów (state, getters, mutations, actions), Pinia operuje na trzech (state, getters, actions).
| Cecha | Pinia 3 | Vuex 4 | |-------|---------|--------| | Mutacje | Brak (bezpośrednie zmiany stanu) | Wymagane do zmian stanu | | TypeScript | Pełna inferencja typów, bez augmentacji | Wymagana ręczna augmentacja typów | | Architektura store | Wiele płaskich store'ów | Jeden store z zagnieżdżonymi modułami | | Composition API | Natywne wsparcie | Bazuje na Options API | | Rozmiar bundla | ~1 KB gzipped | ~6 KB gzipped | | Vue Devtools | Pełne wsparcie (v7) | Pełne wsparcie | | SSR | Wbudowane | Wymaga konfiguracji | | Hot Module Replacement | Wbudowane | Ręczna konfiguracja |
Definiowanie store'ów: Options API vs Setup Syntax
Pinia oferuje dwie składnie definiowania store'ów. Składnia Options odzwierciedla strukturę Vuex, co ułatwia migrację. Składnia Setup wykorzystuje Composition API z Vue 3, zapewniając maksymalną elastyczność.
import { defineStore } from 'pinia'
// Options Store syntax — familiar to Vuex developers
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
lastUpdated: null as Date | null,
}),
getters: {
// Getters receive state as first argument with full type inference
doubleCount: (state) => state.count * 2,
isPositive(): boolean {
// Access other getters via `this`
return this.count > 0
},
},
actions: {
increment() {
// Direct state mutation — no commit() needed
this.count++
this.lastUpdated = new Date()
},
async fetchCount(id: string) {
// Async actions work without extra configuration
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
this.count = data.count
},
},
})Składnia Options mapuje się wprost z konceptów Vuex. State zastępuje state z Vuex, gettery pozostają getterami, a akcje pochłaniają zarówno akcje, jak i mutacje Vuex.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup Store syntax — identical patterns to Composition API
export const useCounterStore = defineStore('counter', () => {
// ref() becomes state
const count = ref(0)
const lastUpdated = ref<Date | null>(null)
// computed() becomes getters
const doubleCount = computed(() => count.value * 2)
const isPositive = computed(() => count.value > 0)
// Functions become actions
function increment() {
count.value++
lastUpdated.value = new Date()
}
async function fetchCount(id: string) {
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
count.value = data.count
}
// Must return all state, getters, and actions
return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})Składnia Setup zapewnia pełną elastyczność: watchery, reuzywalność composable'i oraz logika warunkowa działają naturalnie wewnątrz definicji store'a. Kompromisem jest konieczność jawnego zwracania każdej reaktywnej właściwości i metody.
Integracja z TypeScript: Przewaga Pinia nad Vuex
Wsparcie dla TypeScript to obszar, w którym Pinia zdecydowanie wygrywa. Vuex 4 wymaga ręcznej augmentacji typów poprzez deklaracje modułów, a złożone zagnieżdżone moduły sprawiają, że inferencja typów staje się krucha. Pinia inferuje typy automatycznie na podstawie definicji store'a.
// Vuex 4 — manual type augmentation required
import { Store } from 'vuex'
declare module 'vuex' {
interface State {
counter: {
count: number
lastUpdated: Date | null
}
}
}
// Accessing state requires type assertions or custom helpers
const count = (store.state as { counter: { count: number } }).counter.count// Pinia — full type inference, zero configuration
const counter = useCounterStore()
// TypeScript knows counter.count is number
// TypeScript knows counter.doubleCount is number
// TypeScript knows counter.increment() returns void
// TypeScript knows counter.fetchCount() returns Promise<void>
counter.increment()
console.log(counter.doubleCount)Pinia obsługuje również generyczne store'y dla wzorców reuzywalnych — możliwość, która w Vuex wymaga znacznego boilerplate'u.
Composition API i integracja z composable'ami
Store'y Setup w Pinia akceptują dowolny composable Vue, co umożliwia zaawansowane wzorce, takie jak współdzielone narzędzia VueUse czy stan świadomy routera.
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'
export const useSearchStore = defineStore('search', () => {
const route = useRoute()
const query = ref('')
const results = ref<SearchResult[]>([])
const isLoading = ref(false)
// VueUse composable used directly inside the store
const debouncedSearch = useDebounceFn(async (term: string) => {
if (!term.trim()) {
results.value = []
return
}
isLoading.value = true
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
results.value = await res.json()
} finally {
isLoading.value = false
}
}, 300)
// Watcher syncs URL query param to store state
watch(() => route.query.q, (q) => {
if (typeof q === 'string') {
query.value = q
debouncedSearch(q)
}
})
function setQuery(term: string) {
query.value = term
debouncedSearch(term)
}
return { query, results, isLoading, setQuery }
})Ten wzorzec jest niemożliwy do zrealizowania w Vuex bez obejść. Moduły Vuex nie mogą korzystać z composable'i, watcherów ani hooków routera wewnątrz swojej definicji. Logika store'a musi być rozdzielona między moduł Vuex a zewnętrzne funkcje narzędziowe.
Gotowy na rozmowy o Vue.js / Nuxt.js?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Komunikacja między store'ami i referencje krzyżowe
Vuex wykorzystuje pojedynczy root store z namespace'owanymi modułami. Dostęp do stanu między modułami wymaga rozbudowanych parametrów rootState i rootGetters. Store'y Pinia są niezależne z założenia, a komunikacja między nimi odbywa się poprzez bezpośrednie importy.
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() => {
// Access another store by calling its composable
const productStore = useProductStore()
return items.value.reduce((sum, item) => {
const product = productStore.getById(item.productId)
return sum + (product?.price ?? 0) * item.quantity
}, 0)
})
async function checkout() {
const auth = useAuthStore()
if (!auth.isAuthenticated) {
throw new Error('Authentication required')
}
// Checkout logic
}
return { items, total, checkout }
})Płaska architektura store'ów eliminuje głęboko zagnieżdżone drzewa modułów, które czynią duże aplikacje Vuex trudnymi w nawigacji i refaktoryzacji.
Migracja z Vuex 4 do Pinia 3
Migracja z Vuex do Pinia może przebiegać moduł po module. Obie biblioteki mogą współistnieć w trakcie procesu przejścia. Rekomendowana strategia: zaczynać od modułów liści (store'ów, od których żadne inne moduły nie zależą) i stopniowo przechodzić do modułów centralnych.
Pinia i Vuex mogą działać obok siebie w tej samej aplikacji. Wystarczy zainstalować Pinię obok Vuex, migrować jeden moduł na raz i usunąć Vuex dopiero po przekonwertowaniu wszystkich modułów.
// Before: Vuex module
// store/modules/user.ts
const userModule = {
namespaced: true,
state: () => ({
profile: null as UserProfile | null,
preferences: {} as UserPreferences,
}),
mutations: {
SET_PROFILE(state, profile: UserProfile) {
state.profile = profile
},
UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
state.preferences = { ...state.preferences, ...prefs }
},
},
actions: {
async fetchProfile({ commit }) {
const profile = await api.getProfile()
commit('SET_PROFILE', profile)
},
async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
commit('UPDATE_PREFERENCES', prefs)
},
},
getters: {
isLoggedIn: (state) => state.profile !== null,
displayName: (state) => state.profile?.name ?? 'Guest',
},
}// After: Pinia store
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'
export const useUserStore = defineStore('user', () => {
const profile = ref<UserProfile | null>(null)
const preferences = ref<UserPreferences>({})
const isLoggedIn = computed(() => profile.value !== null)
const displayName = computed(() => profile.value?.name ?? 'Guest')
async function fetchProfile() {
profile.value = await api.getProfile()
}
async function updatePreferences(prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
Object.assign(preferences.value, prefs)
}
return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})Wzorzec migracji jest spójny: mutacje zamieniają się w bezpośrednie przypisania wewnątrz akcji, wywołania commit() znikają, a helpery mapState/mapGetters zostają zastąpione destrukturyzacją composable'a store'a.
Hydratacja stanu SSR z Pinia i Nuxt 4
Renderowanie po stronie serwera wprowadza złożoność w zarządzaniu stanem. Pinia obsługuje serializację i hydratację stanu SSR automatycznie w Nuxt 4, podczas gdy Vuex wymaga ręcznej obsługi window.__INITIAL_STATE__.
Store'y Setup korzystające z composable'i takich jak useRoute() lub useFetch() wymagają ostrożnej obsługi w kontekście SSR. Te composable'e muszą być wywoływane wyłącznie podczas fazy setup, nigdy wewnątrz asynchronicznych callbacków.
import { defineStore } from 'pinia'
export const useProductStore = defineStore('products', {
state: () => ({
items: [] as Product[],
selectedCategory: 'all',
}),
actions: {
async fetchProducts() {
// useFetch is Nuxt-specific — use $fetch for store actions
this.items = await $fetch<Product[]>('/api/products', {
query: { category: this.selectedCategory },
})
},
},
getters: {
getById: (state) => (id: string) =>
state.items.find((item) => item.id === id),
filteredCount: (state) =>
state.items.filter((p) => p.inStock).length,
},
})Nuxt 4 automatycznie serializuje stan Pinia na serwerze i hydratuje go na kliencie. Żadna ręczna logika replaceState() ani dehydratacji nie jest potrzebna.
Porównanie wydajności i wpływ na bundle
Pinia waży około 1 KB po gzip, co stanowi 6-krotnie mniejszy rozmiar w porównaniu z ~6 KB Vuex. W aplikacjach z code splittingiem store'y Pinia podlegają tree-shakingowi: nieużywane store'y i ich zależności są wykluczane z bundli produkcyjnych.
Różnice w wydajności runtime są pomijalne dla większości aplikacji. Obie biblioteki wykorzystują system reaktywności Vue pod spodem. Praktyczny zysk wydajnościowy wynika z mniejszego boilerplate'u, prowadzącego do mniejszej liczby ponownych renderowań spowodowanych zbyt szerokimi subskrypcjami stanu — płaski model store'ów Pinia zachęca do tworzenia granularnych store'ów, co oznacza, że komponenty subskrybują mniej stanu.
Wraz z wprowadzeniem Vapor Mode w Vue 3.6, zarówno Pinia, jak i Vuex korzystają z szybszego pipeline'u renderowania. Jednak ściślejsza integracja Pinia z Composition API sprawia, że jest lepiej przygotowana na optymalizacje Vapor Mode.
Najczęstsze pytania rekrutacyjne o zarządzanie stanem Vue
Rozmowy kwalifikacyjne na stanowiska programistów Vue regularnie testują wiedzę z zakresu zarządzania stanem. Poniżej znajdują się pytania, które pojawiają się najczęściej w 2026 roku.
P: Dlaczego warstwa mutacji z Vuex została usunięta w Pinia?
Mutacje istniały w Vuex, aby umożliwić debugowanie z podróżą w czasie (time-travel debugging) w DevTools. Każda zmiana stanu musiała przechodzić przez synchroniczną mutację, aby DevTools mogły zapisywać snapshoty. Pinia osiąga tę samą funkcjonalność debugowania, śledząc zmiany stanu na poziomie reaktywności, co czyni jawną warstwę mutacji zbędną. Rezultat: mniej boilerplate'u, ta sama moc debugowania.
P: Kiedy preferować Setup store zamiast Options store?
Setup store'y są lepszym wyborem, gdy store potrzebuje composable'i (useRoute, useDebounceFn), złożonych watcherów lub współdzielonej logiki z zewnętrznych composable'i. Options store'y sprawdzają się dobrze w przypadku prostego stanu CRUD z nieskomplikowanymi getterami. W praktyce Setup store'y są częściej spotykane w produkcyjnych bazach kodu, ponieważ odzwierciedlają wzorce Composition API z komponentów.
P: Jak Pinia obsługuje reaktywność dla zagnieżdżonych obiektów?
Pinia wykorzystuje reactive() Vue dla całego obiektu stanu oraz ref() dla poszczególnych właściwości w Setup store'ach. Głęboka reaktywność obowiązuje domyślnie — mutacje zagnieżdżonych obiektów są śledzone automatycznie. W przypadkach wrażliwych na wydajność, z dużymi zbiorami danych, shallowRef() pozwala zrezygnować z głębokiej reaktywności.
P: Jak działają zależności między store'ami w Pinia w porównaniu z Vuex?
W Vuex dostęp między modułami wykorzystuje parametry rootState i rootGetters w akcjach, tworząc niejawne sprzężenia. W Pinia store'y importują się nawzajem bezpośrednio poprzez wywołania useOtherStore(). To sprawia, że zależności są jawne, a TypeScript weryfikuje je w czasie kompilacji. Zależności cykliczne działają w Pinia, pod warunkiem że nie są wywoływane podczas początkowej fazy setup.
P: Co dzieje się ze stanem Pinia podczas hydratacji SSR?
Podczas SSR Pinia serializuje stany wszystkich aktywnych store'ów do JSON i osadza wynik w payloadzie HTML. Na kliencie Pinia hydratuje każdy store przed zamontowaniem komponentów. Stan ustawiony podczas renderowania po stronie serwera jest dostępny natychmiast na kliencie, bez duplikowania wywołań API. W Nuxt 4 cały ten proces jest w pełni automatyczny.
Więcej materiałów do przygotowania do rozmów kwalifikacyjnych z Vue i Nuxt można znaleźć w module o zarządzaniu stanem oraz w module o composable'ach Vue.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Podsumowanie
- Pinia 3 to oficjalna biblioteka zarządzania stanem Vue — Vuex jest w trybie maintenance bez planowanego wydania v5
- Warstwa mutacji została usunięta: akcje Pinia modyfikują stan bezpośrednio, redukując boilerplate o ~40%
- Inferencja TypeScript działa od razu po instalacji w Pinia bez żadnej augmentacji, w przeciwieństwie do ręcznych deklaracji typów Vuex
- Setup store'y integrują się płynnie z Composition API Vue 3, umożliwiając reuzywalność composable'i i watcherów wewnątrz store'ów
- Płaska architektura store'ów zastępuje zagnieżdżone drzewo modułów Vuex, czyniąc komunikację między store'ami jawną i bezpieczną typowo
- Migracja może przebiegać inkrementalnie: Pinia i Vuex współistnieją, umożliwiając konwersję moduł po module
- Hydratacja SSR jest automatyczna w Nuxt 4 z Pinią — żadna ręczna serializacja nie jest potrzebna
- Dla wszystkich nowych projektów Vue 3 w 2026 roku Pinia jest oczywistym wyborem z lepszym DX, mniejszymi bundlami i silniejszym wsparciem ekosystemu
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Nuxt 3: SSR i generowanie statyczne, kompletny przewodnik
Opanowanie SSR i generowania statycznego w Nuxt 3. Od useFetch po route rules: jak optymalizować wydajność aplikacji Vue.js.

Kluczowe pytania rekrutacyjne Vue.js: 25 pytań na zdobycie pracy
Przygotowanie do rozmów Vue.js dzięki 25 kluczowym pytaniom. Od reaktywności po composables, opanuj najważniejsze pojęcia przed kolejną rozmową.

Vue 3 Composition API: Kompletny przewodnik po reaktywności i kompozycji
Praktyczny przewodnik po Vue 3 Composition API. Ref, reactive, computed, watch i composables — wszystko, czego potrzeba do budowania wydajnych aplikacji Vue.