第五章: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 小时前
Vue3+iconfont图标选择器封装
前端·vue
是梦终空3 天前
计算机源码273—基于SpringBoot+Vue3停车场管理系统带支沙箱支付(源代码+数据库)
数据库·spring boot·vue·mybatis·停车场管理系统·沙箱支付·毕设设计
_Twink1e3 天前
基于Vue的纯前端的库存销售系统
前端·vue.js·vue·web
灵魂学者3 天前
使用 Electron 打包项目构建 .EXE 桌面应用程序(简)
electron·node.js·vue·build·桌面应用程序
Zephyr_03 天前
SQL,MyBatis-Plus,maven,Spring与VUE3
sql·spring·vue·maven·mybatis
:mnong4 天前
附图报价系统设计分析5
electron·pdf·vue·cad·dwg·定额
桃花键神4 天前
【2026精品项目】基于SpringBoot3+Vue3的旧物置换系统(包含源码+项目文档+SQL脚本+部署教程)
数据库·spring boot·sql·vue
FlyWIHTSKY4 天前
**Vue 3 `<script setup>` 语法糖** 中的一行解构赋值,用来**从父组件透传下来的属性(attrs)
vue
CAE虚拟与现实4 天前
前后端调试常用工具大全
前端·后端·vue·react·angular