Nuxt 3 状态管理完整指南 - Pinia 实战
Pinia 是 Vue 3 的官方推荐状态管理库。本文详细讲解如何在 Nuxt 3 中高效使用 Pinia。
1. Pinia 基础概念
什么是 Pinia
Pinia vs Vuex:
├─ 语法更简洁 (无需 mutations)
├─ 完整的 TypeScript 支持
├─ 多个 store 可互相依赖
├─ 体积更小 (约 2KB)
├─ Vue DevTools 集成
└─ Hot Module Replacement
安装和配置
# Nuxt 3 已内置 Pinia,无需单独安装
pnpm add pinia @pinia/nuxt
# nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
storesDirs: ['./stores/**']
}
})
2. Store 设计模式
模式 1: 选项式 API
// stores/useAuthStore.ts
export const useAuthStore = defineStore('auth', {
// 1. 状态
state: () => ({
user: null as User | null,
token: '',
isLoading: false,
error: ''
}),
// 2. 计算属性
getters: {
// 是否已登录
isLoggedIn: (state) => !!state.user,
// 用户权限列表
permissions: (state) => state.user?.permissions || []
},
// 3. 方法 (同步和异步)
actions: {
// 同步方法
setUser(user: User) {
this.user = user
},
// 异步方法
async login(email: string, password: string) {
this.isLoading = true
this.error = ''
try {
const response = await $fetch('/api/login', {
method: 'POST',
body: { email, password }
})
this.user = response.user
this.token = response.token
} catch (e: any) {
this.error = e.message
throw e
} finally {
this.isLoading = false
}
},
// 异步方法 2
async logout() {
await $fetch('/api/logout', { method: 'POST' })
this.user = null
this.token = ''
}
}
})
// 使用
<script setup lang="ts">
const authStore = useAuthStore()
// 访问状态
console.log(authStore.user)
console.log(authStore.isLoading)
// 访问 getter
console.log(authStore.isLoggedIn)
// 调用 action
await authStore.login('user@example.com', 'password')
</script>
模式 2: 组合式 API
// stores/useCounterStore.ts
export const useCounterStore = defineStore('counter', () => {
// 状态
const count = ref(0)
const history = ref<number[]>([])
// 计算属性
const double = computed(() => count.value * 2)
const average = computed(() =>
history.value.length > 0
? history.value.reduce((a, b) => a + b) / history.value.length
: 0
)
// 方法
const increment = () => {
count.value++
history.value.push(count.value)
}
const decrement = () => {
count.value--
history.value.push(count.value)
}
const reset = () => {
count.value = 0
history.value = []
}
// 返回暴露的成员
return {
count,
history,
double,
average,
increment,
decrement,
reset
}
})
模式 3: Store 依赖
// stores/useCartStore.ts
export const useCartStore = defineStore('cart', () => {
// 依赖另一个 store
const authStore = useAuthStore()
const productStore = useProductStore()
const items = ref<CartItem[]>([])
// 计算总价 (依赖 productStore)
const total = computed(() =>
items.value.reduce((sum, item) => {
const product = productStore.getProduct(item.productId)
return sum + (product?.price || 0) * item.quantity
}, 0)
)
// 添加到购物车 (检查认证)
const addItem = (productId: string, quantity: number) => {
if (!authStore.isLoggedIn) {
navigateTo('/login')
return
}
items.value.push({ productId, quantity })
}
// 结账 (调用其他 store 的 action)
const checkout = async () => {
// 先获取最新的产品信息
await productStore.fetchProducts()
// 然后提交订单
const order = await $fetch('/api/orders', {
method: 'POST',
body: {
userId: authStore.user?.id,
items: items.value,
total: total.value
}
})
items.value = []
return order
}
return {
items,
total,
addItem,
checkout
}
})
3. 实战案例
案例 1: 用户认证管理
// stores/useAuthStore.ts
interface User {
id: string
email: string
name: string
avatar?: string
role: 'user' | 'admin' | 'teacher'
}
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as User | null,
token: null as string | null,
loading: false,
error: null as string | null
}),
getters: {
isLoggedIn: (state) => !!state.user,
isAdmin: (state) => state.user?.role === 'admin',
isTeacher: (state) => state.user?.role === 'teacher',
// 权限检查
hasPermission: (state) => (permission: string) => {
if (state.user?.role === 'admin') return true
return state.user?.permissions?.includes(permission) || false
}
},
actions: {
// 从 token 恢复用户
async restoreUser() {
const token = useCookie('token')
if (!token.value) return
try {
const user = await $fetch('/api/me', {
headers: { Authorization: `Bearer ${token.value}` }
})
this.user = user
this.token = token.value
} catch {
useCookie('token').value = null
}
},
// 登录
async login(email: string, password: string) {
this.loading = true
this.error = null
try {
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: { email, password }
})
this.user = response.user
this.token = response.token
useCookie('token').value = response.token
} catch (e: any) {
this.error = e.data?.message || '登录失败'
throw e
} finally {
this.loading = false
}
},
// 注册
async signup(data: { email: string; password: string; name: string }) {
this.loading = true
this.error = null
try {
const response = await $fetch('/api/auth/signup', {
method: 'POST',
body: data
})
this.user = response.user
this.token = response.token
useCookie('token').value = response.token
} catch (e: any) {
this.error = e.data?.message || '注册失败'
throw e
} finally {
this.loading = false
}
},
// 登出
async logout() {
try {
await $fetch('/api/auth/logout', { method: 'POST' })
} finally {
this.user = null
this.token = null
useCookie('token').value = null
}
}
}
})
案例 2: 表单状态管理
// stores/useFormStore.ts
interface FormData {
[key: string]: any
}
export const useFormStore = defineStore('form', () => {
// 多个表单的状态
const forms = ref<Map<string, FormData>>(new Map())
const errors = ref<Map<string, Record<string, string>>>(new Map())
const loading = ref<Map<string, boolean>>(new Map())
// 初始化表单
const initForm = (formId: string, initialData: FormData = {}) => {
forms.value.set(formId, { ...initialData })
errors.value.set(formId, {})
}
// 更新表单字段
const updateField = (formId: string, field: string, value: any) => {
const form = forms.value.get(formId)
if (form) {
form[field] = value
// 实时验证
validateField(formId, field)
}
}
// 批量更新表单
const updateForm = (formId: string, data: FormData) => {
forms.value.set(formId, {
...forms.value.get(formId),
...data
})
}
// 验证单个字段
const validateField = (formId: string, field: string) => {
const form = forms.value.get(formId)
const fieldErrors = errors.value.get(formId) || {}
const value = form?.[field]
// 简单验证规则
if (field === 'email' && value) {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
if (!isValid) {
fieldErrors[field] = '请输入有效的邮箱'
} else {
delete fieldErrors[field]
}
}
errors.value.set(formId, fieldErrors)
}
// 验证整个表单
const validateForm = (formId: string) => {
// 验证所有字段
const form = forms.value.get(formId)
if (!form) return true
Object.keys(form).forEach(field => {
validateField(formId, field)
})
const fieldErrors = errors.value.get(formId)
return !fieldErrors || Object.keys(fieldErrors).length === 0
}
// 提交表单
const submitForm = async (formId: string, submitHandler: (data: FormData) => Promise<any>) => {
if (!validateForm(formId)) {
return false
}
loading.value.set(formId, true)
try {
const form = forms.value.get(formId)
const result = await submitHandler(form || {})
// 清空表单
forms.value.delete(formId)
errors.value.delete(formId)
return result
} catch (error: any) {
// 处理错误
if (error.data?.errors) {
errors.value.set(formId, error.data.errors)
}
throw error
} finally {
loading.value.set(formId, false)
}
}
// 重置表单
const resetForm = (formId: string) => {
forms.value.delete(formId)
errors.value.delete(formId)
loading.value.delete(formId)
}
return {
forms,
errors,
loading,
initForm,
updateField,
updateForm,
validateField,
validateForm,
submitForm,
resetForm
}
})
// 在组件中使用
<script setup lang="ts">
const formStore = useFormStore()
onMounted(() => {
formStore.initForm('loginForm', {
email: '',
password: ''
})
})
const email = computed({
get: () => formStore.forms.get('loginForm')?.email || '',
set: (value) => formStore.updateField('loginForm', 'email', value)
})
const handleSubmit = async () => {
await formStore.submitForm('loginForm', async (data) => {
return await useAuthStore().login(data.email, data.password)
})
}
</script>
4. Store 最佳实践
// ✅ 最佳实践检查清单
// 1. 职责分离
// ✅ 好: 明确的职责
stores/
├── useAuthStore.ts (用户认证)
├── useProductStore.ts (产品数据)
├── useCartStore.ts (购物车)
└── useUIStore.ts (UI 状态)
// ❌ 避免: 一个大的 store
stores/
└── useAppStore.ts (什么都做)
// 2. 命名约定
// ✅ 好: 清晰的命名
const authStore = useAuthStore()
const userList = authStore.users
// ❌ 避免: 模糊的命名
const store = useStore()
const list = store.l
// 3. 类型安全
interface User {
id: string
name: string
email: string
}
export const useAuthStore = defineStore('auth', {
state: (): { user: User | null } => ({
user: null
})
})
// 4. 避免直接修改状态
// ✅ 好: 通过 action 修改
authStore.setUser(newUser)
// ❌ 避免: 直接修改
authStore.user = newUser
// 5. 异步操作规范
async fetchData() {
this.loading = true
this.error = null
try {
const data = await fetchFromAPI()
this.data = data
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
}
5. 调试和监控
// 1. Pinia DevTools (自动集成)
// 在浏览器 Vue DevTools 中:
// - 查看所有 store 的状态
// - 追踪 actions 的执行
// - 时间旅行调试
// 2. 手动日志
export const useAuthStore = defineStore('auth', {
actions: {
async login(email: string, password: string) {
console.log('[Auth] 开始登录', { email })
// ...
console.log('[Auth] 登录成功', { user: this.user })
}
}
})
// 3. 监控状态变化
const authStore = useAuthStore()
watch(
() => authStore.user,
(newUser, oldUser) => {
console.log('用户状态改变:', { from: oldUser, to: newUser })
}
)
// 4. 性能监控
export const useAuthStore = defineStore('auth', {
actions: {
async heavyOperation() {
const start = performance.now()
// 执行操作
const duration = performance.now() - start
console.log(`操作耗时: ${duration}ms`)
}
}
})
总结
| 特性 | 优势 |
|---|---|
| 灵活的状态管理 | 支持选项式和组合式 API |
| 完整的类型支持 | TypeScript 集成 |
| 简洁的语法 | 无需 mutations |
| Store 间协作 | 轻松共享数据 |
| 开发工具集成 | DevTools 支持 |


