Skip to content

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。

基于 VitePress 构建

总访问量 访客数