Vue.jsMiddleTechnical
Что такое Pinia? Чем она отличается от Vuex и почему является рекомендованным менеджером состояния для Vue 3?
Pinia — официальный стор для Vue 3, заменивший Vuex: нет mutations, нет namespaced-модулей, полный TypeScript inference, два стиля API (Options и Setup). Vuex 5 так и не вышел, поэтому Pinia стала рекомендованным решением.
Что такое Pinia
Pinia — официальный менеджер состояния Vue 3, пришедший на смену Vuex. Разработана членом команды Vue Эдуардо Сан-Мартином, включена в экосистему Vue как рекомендованное решение начиная с Vue 3.2+. Реализована поверх Composition API и Proxy-реактивности Vue 3.
Минимальный пример
// stores/counter.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubled, increment };
});
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
app.use(createPinia());
app.mount('#app');
// MyComponent.vue (script setup)
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
const store = useCounterStore();
const { count, doubled } = storeToRefs(store); // реактивная деструктуризация
Отличия от Vuex
- Нет mutations. В Vuex изменение состояния требовало двух шагов: action → mutation. В Pinia состояние меняется напрямую внутри action или через
store.count++. - Нет namespaced-модулей. Каждый store — отдельный вызов
defineStore()с уникальным id. Импорт явный, нет строковых путей вида'auth/login'. - TypeScript из коробки. Типы выводятся автоматически без ручных деклараций, в отличие от Vuex 4, где требовались дополнительные typed wrappers.
- Devtools. Pinia интегрируется с Vue Devtools: timeline actions, time-travel для каждого store, hot-module replacement состояния.
- Два стиля API. Options Store (похож на компоненты с
state,getters,actions) и Setup Store (обычная setup-функция с ref/computed). Оба поддерживаются официально.
Options Store (альтернативный синтаксис)
export const useCartStore = defineStore('cart', {
state: () => ({ items: [], total: 0 }),
getters: {
itemCount: (state) => state.items.length,
},
actions: {
addItem(product) {
this.items.push(product);
this.total += product.price;
},
async fetchCart(userId) {
const data = await fetch(`/api/cart/${userId}`).then(r => r.json());
this.items = data.items;
}
}
});
Плагины Pinia
Pinia поддерживает плагины — функции, получающие контекст каждого store. Например, плагин персистентности:
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
Затем в store добавляется persist: true в опции defineStore.
Почему Pinia рекомендована для Vue 3
- Vuex 5 (изначально планировавшийся для Vue 3) так и не вышел — Pinia стала его заменой.
- Нативно использует Composition API, что делает код stores идентичным composables по стилю.
- SSR-поддержка (Nuxt 3) работает без дополнительной конфигурации.
- Тестирование:
setActivePinia(createPinia())в beforeEach — store изолирован для каждого теста.
Подводные камни
- Деструктуризация store напрямую (
const { count } = useCounterStore()) разрушает реактивность — нуженstoreToRefs()для state/getters и обычная деструктуризация для actions. - Вызов store вне компонента (например, в router guard) должен происходить после
app.use(pinia), иначе — ошибка «no active pinia». - В SSR каждый запрос должен создавать новый экземпляр pinia — общий singleton приведёт к утечке состояния между пользователями.
- Прямое изменение
store.$stateв продакшене допустимо, но в тестах лучше использоватьstore.$patch()для читаемости. - При использовании Setup Store нет автоматического reset — нужно реализовывать
$reset()вручную черезstore.$patch(initialState). - Circular imports между stores (store A вызывает useStoreB внутри action, store B — useStoreA) создают проблемы при холодном старте — рефакторить через lazy import внутри action.
- HMR работает только если добавить блок
if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useStore, import.meta.hot)) }.
Common mistakes
- Перенести Vuex-привычку с mutations и писать в Pinia такой же boilerplate.
- Деструктурировать стор без
storeToRefsи терять реактивность. - Хранить во Pinia то, что должно быть локально в компоненте.
- Прятать API-запросы в getters/computed вместо actions.
What the interviewer is testing
- Знает базовый API
defineStore+ setup-style. - Может объяснить разницу с Vuex: нет mutations, лучше TS.
- Понимает роль
storeToRefs. - Помнит про SSR-гидрацию состояния.