Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Pinia - глобальное хранилище состояния для Vue
Что такое Pinia
Pinia - это официальное хранилище состояния (state management) для Vue 3. Это современная замена Vuex, которая более простая, типизированная и легкая в использовании.
Пиния позволяет хранить глобальное состояние приложения и получать доступ к нему из любого компонента, без необходимости пробрасывать props через десятки уровней.
Когда используется Pinia
Pinia нужна, когда:
- Состояние нужно в разных компонентах
// Два несвязанных компонента нужны одни и те же данные
// UserProfile.vue - показывает профиль
// Header.vue - показывает имя пользователя в шапке
// Оба нужны userData - это идеальный случай для Pinia
- Состояние сложное и меняется часто
// Состояние юзера с множеством вложенных свойств
// Фильтры, сортировка, пагинация
// Несколько асинхронных операций
- Нужна совместная история изменений
// Откат (undo), повтор (redo)
// Debug инструменты (Vue DevTools)
// Время путешествия (time travel debugging)
Без Pinia (проблемы)
<!-- Без Pinia приходится пробрасывать props через множество компонентов -->
<!-- App.vue -->
<template>
<Header :user="user" @logout="logout" />
<MainContent :user="user" />
<Footer :user="user" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const user = ref(null);
const logout = () => { user.value = null; };
return { user, logout };
}
};
</script>
<!-- Header.vue -->
<template>
<header>
<p>{{ user?.name }}</p>
<button @click="$emit('logout')">Logout</button>
</header>
</template>
<script>
export default {
props: ['user'],
emits: ['logout']
};
</script>
<!-- MainContent.vue - нужно пробросить дальше -->
<template>
<div>
<UserProfile :user="user" />
</div>
</template>
<script>
export default {
props: ['user']
};
</script>
<!-- UserProfile.vue - в итоге получает props -->
<template>
<div>{{ user?.name }} - {{ user?.email }}</div>
</template>
<script>
export default {
props: ['user']
};
</script>
Проблемы:
- Props drilling (пробрасывание через множество уровней)
- Сложно отследить, откуда приходят данные
- Сложно менять структуру (нужно обновить все компоненты)
С Pinia (чистое решение)
1. Создаем store (хранилище)
// stores/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
// Состояние (state)
const user = ref(null);
const isLoading = ref(false);
const error = ref(null);
// Вычисляемые свойства (getters)
const isAuthenticated = computed(() => user.value !== null);
const userName = computed(() => user.value?.name || 'Guest');
// Методы (actions) - могут быть асинхронными
const fetchUser = async (id) => {
isLoading.value = true;
error.value = null;
try {
const response = await fetch(`/api/users/${id}`);
user.value = await response.json();
} catch (e) {
error.value = e.message;
} finally {
isLoading.value = false;
}
};
const logout = () => {
user.value = null;
};
const updateUser = (updatedData) => {
user.value = { ...user.value, ...updatedData };
};
return {
// State
user,
isLoading,
error,
// Getters
isAuthenticated,
userName,
// Actions
fetchUser,
logout,
updateUser
};
});
2. Используем store в компонентах
<!-- Header.vue - прямой доступ к store, без props -->
<template>
<header>
<p>Welcome, {{ userStore.userName }}!</p>
<button @click="userStore.logout">Logout</button>
</header>
</template>
<script setup>
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
</script>
<!-- UserProfile.vue - тоже прямой доступ -->
<template>
<div v-if="userStore.isLoading">Loading...</div>
<div v-else>
<p>{{ userStore.user?.name }}</p>
<p>{{ userStore.user?.email }}</p>
<button @click="handleUpdate">Update Profile</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
const handleUpdate = () => {
userStore.updateUser({ name: 'New Name' });
};
</script>
<!-- App.vue - очень просто -->
<template>
<Header />
<MainContent />
<Footer />
</template>
<script setup>
// Не нужно передавать props!
</script>
Структура Pinia store
// Формат: Composition API style (рекомендуемый)
export const useCounterStore = defineStore('counter', () => {
// 1. STATE - реактивные переменные
const count = ref(0);
const doubled = ref(false);
// 2. GETTERS - вычисляемые свойства
const doubledCount = computed(() => count.value * 2);
const description = computed(() =>
`Count is ${count.value} ${doubled.value ? '(doubled)' : ''}`
);
// 3. ACTIONS - методы для изменения состояния
const increment = () => count.value++;
const incrementBy = (amount) => count.value += amount;
const double = () => {
doubled.value = !doubled.value;
};
// Асинхронный action
const asyncIncrement = async (delay = 1000) => {
await new Promise(resolve => setTimeout(resolve, delay));
count.value++;
};
// 4. RETURN - экспортируем в компоненты
return {
// State
count,
doubled,
// Getters
doubledCount,
description,
// Actions
increment,
incrementBy,
double,
asyncIncrement
};
});
Преимущества Pinia
1. Простота
// Просто функция, возвращающая объект
// Не нужно разбираться в mutations, actions, getters отдельно
// Как обычный JavaScript
2. TypeScript поддержка
// Полная типизация "из коробки"
const userStore = useUserStore();
userStore.user.name; // TypeScript знает тип и подсказывает
userStore.fetchUser('123'); // Типы аргументов проверены
3. Reactivity
// Автоматическое обновление компонентов при изменении state
const count = ref(0);
count.value++; // Все компоненты, использующие store, обновятся
4. DevTools интеграция
// Можно видеть все изменения состояния
// Time travel debugging
// Экспорт/импорт состояния
5. Модульность
// Разные store независимо друг от друга
const userStore = useUserStore();
const cartStore = useCartStore();
const settingsStore = useSettingsStore();
Пример: Интернет магазин
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([]);
const isLoading = ref(false);
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const isEmpty = computed(() => items.value.length === 0);
const addItem = (product: Product) => {
const existingItem = items.value.find(i => i.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
items.value.push({ ...product, quantity: 1 });
}
};
const removeItem = (productId: string) => {
items.value = items.value.filter(i => i.id !== productId);
};
const checkout = async () => {
isLoading.value = true;
try {
const response = await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ items: items.value })
});
const order = await response.json();
items.value = []; // Очищаем корзину после заказа
return order;
} finally {
isLoading.value = false;
}
};
const clear = () => {
items.value = [];
};
return { items, total, isEmpty, isLoading, addItem, removeItem, checkout, clear };
});
<!-- ShoppingCart.vue -->
<template>
<div>
<h2>Cart ({{ cartStore.items.length }} items)</h2>
<div v-if="cartStore.isEmpty">Your cart is empty</div>
<div v-else>
<div v-for="item in cartStore.items" :key="item.id">
{{ item.name }} x{{ item.quantity }} = ${{ item.price * item.quantity }}
<button @click="cartStore.removeItem(item.id)">Remove</button>
</div>
<p>Total: ${{ cartStore.total }}</p>
<button @click="handleCheckout" :disabled="cartStore.isLoading">
{{ cartStore.isLoading ? 'Processing...' : 'Checkout' }}
</button>
</div>
</div>
</template>
<script setup>
import { useCartStore } from '@/stores/cart';
const cartStore = useCartStore();
const handleCheckout = async () => {
const order = await cartStore.checkout();
console.log('Order created:', order);
};
</script>
Когда Pinia может быть избыточной
Не используй Pinia, если:
- Состояние локально для одного компонента (используй
ref,useState) - Очень простое приложение без общего состояния
- Состояние не меняется часто
Заключение
Pinia нужна для:
- Глобального состояния - доступ из любого компонента
- Сложного состояния - множество полей и операций
- Асинхронных операций - загрузка данных, API запросы
- Отладки - время путешествия, просмотр истории изменений
В Vue 3 это стандартное решение для управления состоянием. Pinia проще Vuex и стала официальной рекомендацией Vue команды.