第五章:Pinia 状态管理

Pinia 是 Vue 3 官方推荐的状态管理库,相比 Vuex 更轻量、类型推断更好、支持组合式 API 写法。


5.1 为什么用 Pinia

对比维度 Vuex 4 Pinia
TypeScript 支持 一般(需手动类型) 完整开箱即用
API 复杂度 mutation/action/module 只有 state/getters/actions
组合式 API 不支持 支持 setup store
DevTools 支持 支持(更好的时间旅行)
Bundle Size ~10KB ~1.5KB
代码分割 需要配置 天然支持

5.2 定义 Store

选项式写法(Options Store)

ts 复制代码
// stores/counter.ts
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // state:相当于 data
  state: () => ({
    count: 0,
    name: 'Counter'
  }),

  // getters:相当于 computed
  getters: {
    doubled: (state) => state.count * 2,
    isPositive: (state) => state.count > 0,
    // 引用其他 getter(使用 this)
    tripled(): number { return this.doubled * 1.5 }
  },

  // actions:相当于 methods,支持异步
  actions: {
    increment() { this.count++ },
    async fetchAndSet() {
      const data = await api.getData()
      this.count = data.count
    }
  }
})

组合式写法(Setup Store,推荐)

ts 复制代码
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // ref → state
  const user = ref<User | null>(null)
  const token = ref(localStorage.getItem('token') || '')

  // computed → getters
  const isLoggedIn = computed(() => !!token.value)
  const userName = computed(() => user.value?.name ?? '游客')

  // function → actions
  async function login(email: string, password: string) {
    const data = await authApi.login({ email, password })
    token.value = data.token
    user.value = data.user
    localStorage.setItem('token', data.token)
  }

  function logout() {
    user.value = null
    token.value = ''
    localStorage.removeItem('token')
  }

  // 必须 return 所有东西
  return { user, token, isLoggedIn, userName, login, logout }
})

5.3 使用 Store

vue 复制代码
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

// ✅ 用 storeToRefs 解构,保持响应式
const { count, doubled, isPositive } = storeToRefs(counterStore)

// ✅ actions 可以直接解构(不是响应式,无需 storeToRefs)
const { increment, fetchAndSet } = counterStore

// ❌ 错误做法:直接解构 state 失去响应式
// const { count } = counterStore  // count 不会更新!
</script>

<template>
  <p>{{ count }} × 2 = {{ doubled }}</p>
  <button @click="increment">+1</button>
  <button @click="counterStore.$patch({ count: 10 })">设为 10</button>
</template>

5.4 $patch 批量修改

ts 复制代码
const store = useUserStore()

// 方式1:对象 patch(简单场景)
store.$patch({
  name: '新名字',
  email: 'new@email.com'
})

// 方式2:函数 patch(推荐,支持复杂操作)
store.$patch((state) => {
  state.list.push({ id: Date.now(), text: '新条目' })
  state.count++
})

5.5 持久化存储

ts 复制代码
// 方案一:手动
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const useSettingsStore = defineStore('settings', () => {
  const theme = ref(localStorage.getItem('theme') || 'dark')

  watch(theme, (val) => localStorage.setItem('theme', val))

  return { theme }
})

// 方案二:pinia-plugin-persistedstate 插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
pinia.use(piniaPluginPersistedstate)

defineStore('user', { /* ... */ }, {
  persist: {
    key: 'user-store',
    storage: localStorage,
    paths: ['token', 'preferences']  // 只持久化指定字段
  }
})

5.6 Store 间相互调用

ts 复制代码
// stores/cart.ts
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', () => {
  const userStore = useUserStore()  // 直接调用其他 store

  async function checkout() {
    if (!userStore.isLoggedIn) {
      throw new Error('请先登录')
    }
    // ...
  }

  return { checkout }
})

章节总结

知识点 核心要点 重要程度
defineStore 组合式写法更推荐,灵活性更高 ⭐⭐⭐⭐⭐
storeToRefs 解构 state/getters 时必须用 ⭐⭐⭐⭐⭐
$patch 批量修改,函数形式支持复杂操作 ⭐⭐⭐⭐
持久化 手动 or pinia-plugin-persistedstate ⭐⭐⭐⭐
Store 间调用 直接在 action 中引用其他 store ⭐⭐⭐⭐

Demo 说明

Demo 位于 vue_demos/src/views/Chapter05_Pinia/PiniaDemo.vue

  • Counter Store(组合式):storeToRefs、increment/reset、变更历史
  • User Store(选项式):登录/登出、Getters 推导值展示
  • Cart Store:购物车增删、购物车角标跨组件共享(App Header)

🔗 专栏链接Vue 3 全栈开发实战专栏

📦 项目源码资源点击下载项目源码

相关推荐
文阿花10 小时前
大屏地图实现方案之-高德(二)
vue·地图·高德
森林的尽头是阳光14 小时前
前端使用postman快速造数据
前端·javascript·vue·postman·造数·本地测试
文阿花2 天前
大屏实现方案之-高德
vue·地图·高德
Anesthesia丶3 天前
Vite + Svelte + shadcn-svelte 最小化 Demo+Vue3语法对比总结
vue·vite·svelte·shadcn-svelte
孟郎郎3 天前
TimeoutError: The operation was aborted due to timeout at new DOMException
ai·前端框架·npm·vue·pnpm·deepseek
lpd_lt3 天前
AI生成Spring Boot + Vue 3 + MySQL + MyBatis-Plus的项目实战
java·spring boot·vue·ai编程
来杯@Java3 天前
图书管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·mybatis·课程设计
华大哥4 天前
前后端分离实现五级行政区划树形菜单及设备查询管理
sqlite·vue·springboot
码界筑梦坊4 天前
282-基于Python的豆瓣音乐可视化分析推荐系统
开发语言·python·信息可视化·数据分析·flask·vue
chushiyunen4 天前
滑块验证(滑动验证)
vue