1. Pinia 的基本概念
Pinia 是 Vue 的官方状态管理库,相比 Vuex 更加轻量和简单。主要特点:
-
无需命名空间
-
更好的 TypeScript 支持
-
更简单的 API
-
支持组合式 API
-
无需嵌套模块
2. Store 定义
// stores/counter.ts
import { defineStore } from 'pinia'
// 选项式写法
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
todos: [] as Todo[],
user: null as User | null
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
doneTodos: (state) => state.todos.filter(todo => todo.done),
// 带参数的 getter
getTodoById: (state) => (id: number) => {
return state.todos.find(todo => todo.id === id)
}
},
// 操作方法
actions: {
increment() {
this.count++
},
async fetchTodos() {
const todos = await api.getTodos()
this.todos = todos
}
}
})
// 组合式写法
export const useUserStore = defineStore('user', () => {
// 状态
const token = ref<string | null>(null)
const userInfo = ref<User | null>(null)
// 计算属性
const isLoggedIn = computed(() => !!token.value)
// 操作方法
async function login(credentials: Credentials) {
const response = await api.login(credentials)
token.value = response.token
userInfo.value = response.user
}
function logout() {
token.value = null
userInfo.value = null
}
return {
token,
userInfo,
isLoggedIn,
login,
logout
}
})
3. 在组件中使用
<template>
<div>
<!-- 使用状态 -->
<p>Count: {{ counter.count }}</p>
<p>Double Count: {{ counter.doubleCount }}</p>
<p>User: {{ user.userInfo?.name }}</p>
<!-- 操作状态 -->
<button @click="increment">Increment</button>
<button @click="handleLogin">Login</button>
<button @click="user.logout">Logout</button>
<!-- 使用 getter -->
<ul>
<li v-for="todo in counter.doneTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
const user = useUserStore()
// 解构(使用 storeToRefs 保持响应性)
const { count, doubleCount } = storeToRefs(counter)
const { isLoggedIn } = storeToRefs(user)
// 方法可以直接解构
const { increment } = counter
// 登录处理
const handleLogin = async () => {
await user.login({
username: 'test',
password: '123456'
})
}
// 监听状态变化
watch(
() => counter.count,
(newValue) => {
console.log('count changed:', newValue)
}
)
</script>
4. 持久化插件
// plugins/persistedState.ts
import { createPersistedState } from 'pinia-plugin-persistedstate'
export const piniaPersistedState = createPersistedState({
key: (id) => `store-${id}`,
storage: localStorage,
paths: ['user.token'] // 指定要持久化的路径
})
// main.ts
import { createPinia } from 'pinia'
import { piniaPersistedState } from './plugins/persistedState'
const pinia = createPinia()
pinia.use(piniaPersistedState)
app.use(pinia)
5. 自定义插件
// plugins/resetStore.ts
import { PiniaPluginContext } from 'pinia'
export function resetStore({ store }: PiniaPluginContext) {
const initialState = JSON.parse(JSON.stringify(store.$state))
store.$reset = () => {
store.$patch(initialState)
}
}
// main.ts
const pinia = createPinia()
pinia.use(resetStore)
6. TypeScript 支持
// types/store.ts
export interface User {
id: number
name: string
email: string
}
export interface Todo {
id: number
text: string
done: boolean
}
// stores/counter.ts
import type { User, Todo } from '@/types/store'
interface CounterState {
count: number
todos: Todo[]
}
export const useCounterStore = defineStore('counter', {
state: (): CounterState => ({
count: 0,
todos: []
})
})
7. 实际应用示例
// stores/cart.ts
interface CartItem {
id: number
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),
totalAmount: (state) => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
itemById: (state) => (id: number) => state.items.find(item => item.id === id)
},
actions: {
addItem(product: Product, quantity = 1) {
const existingItem = this.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += quantity
} else {
this.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity
})
}
},
removeItem(id: number) {
const index = this.items.findIndex(item => item.id === id)
if (index > -1) {
this.items.splice(index, 1)
}
},
clearCart() {
this.items = []
},
async checkout() {
try {
await api.createOrder(this.items)
this.clearCart()
return true
} catch (error) {
console.error('Checkout failed:', error)
return false
}
}
}
})
要点:
- Pinia vs Vuex:
-
更简单的 API
-
更好的 TypeScript 支持
-
无需模块嵌套
-
更轻量级
- 状态管理最佳实践:
-
合理划分 store
-
使用 storeToRefs 保持响应性
-
适当使用持久化
-
处理异步操作
- 性能优化:
-
避免不必要的计算属性
-
合理使用缓存
-
按需导入 store
- 调试技巧:
-
Vue DevTools 支持
-
状态快照
-
时间旅行调试
- 实际应用场景:
-
用户认证
-
购物车管理
-
全局配置
-
数据缓存