Pinia 状态管理最佳实践
Pinia 是 Vue 3 官方推荐的状态管理库,替代了 Vuex。它的 API 更简洁,TypeScript 支持更好,没有 mutation 的概念。
定义 Store(Setup Store 写法)
typescript
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const token = ref<string>('')
const profile = ref<User | null>(null)
const isLoggedIn = computed(() => !!token.value)
const displayName = computed(() => profile.value?.name ?? '游客')
async function login(credentials: LoginParams) {
const res = await api.login(credentials)
token.value = res.token
profile.value = res.user
}
function logout() {
token.value = ''
profile.value = null
}
return { token, profile, isLoggedIn, displayName, login, logout }
})在组件中使用
vue
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// 解构 state 和 getter 必须用 storeToRefs 保持响应性
const { isLoggedIn, displayName } = storeToRefs(userStore)
// action 可以直接解构
const { login, logout } = userStore
</script>常见坑:直接解构 state 会失去响应性。必须用
storeToRefs。
Store 间通信
typescript
export const useCartStore = defineStore('cart', () => {
function checkout() {
const userStore = useUserStore() // 在 action 内调用
if (!userStore.isLoggedIn) throw new Error('请先登录')
}
return { checkout }
})注意:Store 的互相调用要在函数体内,不能在模块顶层,否则可能因为初始化顺序导致循环依赖问题。
持久化
方案 1:pinia-plugin-persistedstate
typescript
// Store 定义中添加 persist 选项
export const useUserStore = defineStore('user', () => {
const token = ref('')
return { token }
}, {
persist: {
key: 'auth',
pick: ['token'] // 只持久化 token
}
})方案 2:手动监听
typescript
export const useThemeStore = defineStore('theme', () => {
const mode = ref<'light' | 'dark'>(
(localStorage.getItem('theme') as 'light' | 'dark') ?? 'light'
)
watch(mode, (val) => {
localStorage.setItem('theme', val)
})
return { mode }
})最佳实践
Store 的拆分原则
- 按业务领域拆,不按页面拆。比如 useUserStore、useProductStore。
- 跨组件共享的状态才放 Store,组件内部状态用 ref/reactive。
- 派生数据用 computed,不要在 Store 里存可计算的结果。
类型安全
typescript
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.qty, 0)
)
const payable = computed(() => total.value)
return { items, total, payable }
})小结
Pinia 相比 Vuex 更轻量、更符合 Composition API 的心智模型。掌握 Setup Store 写法、storeToRefs 解构、Store 间通信和持久化方案,就能构建出干净可维护的状态管理架构。记住:不是所有状态都需要全局 Store。