name: pinia description: 用于 Vue 3 的 Pinia 状态管理,包括存储创建、操作、getter、插件和 DevTools 集成。 allowed-tools: 读取、写入、编辑、Bash、Glob、Grep
Pinia 技能
为 Vue 3 应用程序实现 Pinia 状态管理的专家级协助。
能力
- 创建类型安全的 Pinia 存储
- 为异步操作实现 actions
- 定义用于计算状态的 getters
- 配置 Pinia 插件(持久化等)
- 设置存储组合模式
- 与 Vue DevTools 集成
使用场景
在以下情况时调用此技能:
- 在 Vue 中设置全局状态管理
- 创建特定于功能的存储
- 实现持久化状态
- 组合多个存储
- 处理异步状态操作
输入参数
| 参数 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| storeName | 字符串 | 是 | 存储名称(使用前缀) |
| stateShape | 对象 | 是 | 初始状态结构 |
| actions | 数组 | 是 | 存储操作 |
| getters | 数组 | 否 | 计算 getters |
| persist | 布尔值 | 否 | 启用持久化 |
配置示例
{
"storeName": "useUserStore",
"stateShape": {
"user": null,
"isAuthenticated": false
},
"actions": ["login", "logout", "fetchUser"],
"getters": ["fullName", "isAdmin"],
"persist": true
}
存储模式
Setup 存储(推荐)
// stores/user.ts
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
interface User {
id: string;
name: string;
email: string;
role: 'user' | 'admin';
}
export const useUserStore = defineStore('user', () => {
// 状态
const user = ref<User | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
// Getters
const isAuthenticated = computed(() => !!user.value);
const isAdmin = computed(() => user.value?.role === 'admin');
const fullName = computed(() => user.value?.name ?? 'Guest');
// Actions
async function login(email: string, password: string) {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('无效凭证');
}
const data = await response.json();
user.value = data.user;
localStorage.setItem('token', data.token);
} catch (e) {
error.value = (e as Error).message;
throw e;
} finally {
loading.value = false;
}
}
async function logout() {
user.value = null;
localStorage.removeItem('token');
}
async function fetchUser() {
const token = localStorage.getItem('token');
if (!token) return;
loading.value = true;
try {
const response = await fetch('/api/auth/me', {
headers: { Authorization: `Bearer ${token}` },
});
user.value = await response.json();
} catch (e) {
logout();
} finally {
loading.value = false;
}
}
return {
// 状态
user,
loading,
error,
// Getters
isAuthenticated,
isAdmin,
fullName,
// Actions
login,
logout,
fetchUser,
};
});
Options 存储
// stores/cart.ts
import { defineStore } from 'pinia';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
}),
getters: {
totalItems: (state) =>
state.items.reduce((sum, item) => sum + item.quantity, 0),
totalPrice: (state) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
isEmpty: (state) => state.items.length === 0,
},
actions: {
addItem(item: Omit<CartItem, 'quantity'>) {
const existing = this.items.find((i) => i.id === item.id);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...item, quantity: 1 });
}
},
removeItem(id: string) {
const index = this.items.findIndex((i) => i.id === id);
if (index > -1) {
this.items.splice(index, 1);
}
},
updateQuantity(id: string, quantity: number) {
const item = this.items.find((i) => i.id === id);
if (item) {
item.quantity = Math.max(0, quantity);
if (item.quantity === 0) {
this.removeItem(id);
}
}
},
clearCart() {
this.items = [];
},
},
});
Pinia 设置与插件
// main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import App from './App.vue';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
const app = createApp(App);
app.use(pinia);
app.mount('#app');
// 带持久化的存储
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light',
language: 'en',
}),
persist: true, // 持久化到 localStorage
});
// 自定义持久化配置
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: null,
}),
persist: {
key: 'user-store',
storage: sessionStorage,
paths: ['token'], // 仅持久化 token
},
});
存储组合
// stores/checkout.ts
import { defineStore } from 'pinia';
import { useCartStore } from './cart';
import { useUserStore } from './user';
export const useCheckoutStore = defineStore('checkout', () => {
const cart = useCartStore();
const user = useUserStore();
const canCheckout = computed(() => {
return user.isAuthenticated && !cart.isEmpty;
});
async function processCheckout(paymentMethod: string) {
if (!canCheckout.value) {
throw new Error('无法结账');
}
const order = {
userId: user.user!.id,
items: cart.items,
total: cart.totalPrice,
paymentMethod,
};
const response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order),
});
if (response.ok) {
cart.clearCart();
}
return response.json();
}
return {
canCheckout,
processCheckout,
};
});
在组件中使用
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
// 解构并保持响应性
const { user, isAuthenticated, loading } = storeToRefs(userStore);
// Actions 可以直接解构
const { login, logout } = userStore;
async function handleLogin() {
try {
await login(email.value, password.value);
router.push('/dashboard');
} catch (e) {
// 处理错误
}
}
</script>
<template>
<div v-if="loading">加载中...</div>
<div v-else-if="isAuthenticated">
欢迎,{{ user?.name }}
<button @click="logout">登出</button>
</div>
<LoginForm v-else @submit="handleLogin" />
</template>
最佳实践
- 推荐使用 setup 存储以获得更好的 TypeScript 支持
- 使用 storeToRefs 进行响应式解构
- 为复杂功能组合存储
- 保持存储专注于单一职责
- 使用插件处理横切关注点
目标流程
- vue-application-development
- nuxt-full-stack
- state-management-setup
- frontend-architecture