第五章: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 全栈开发实战专栏

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

相关推荐
万亿少女的梦1682 天前
基于Vue、Vite与CesiumJS的江西省铜矿资源WebGIS系统设计
vue·vite·cesiumjs·webgis·空间数据可视化
天丁o2 天前
我把一套 Flowable + Spring Boot + Vue 的 BPM 流程引擎整理开源了:从流程设计器到待办闭环
spring boot·vue·工作流引擎·flowable·bpm流程引擎·流程审批
弹简特2 天前
【Vue3速成】05-vue3官方库路由机制之路由守卫
vue·路由
小堂子这厢有礼了2 天前
Chet.Admin 模块详解⑥:字典管理 + useDict 联动表单
vue·后台管理系统·vbenadmin·chet.admin
猫猫不是喵喵.3 天前
Vue3 hooks 函数
vue
Pu_Nine_95 天前
Vue3+TS+Express 转 Nuxt4 全栈开发笔记(Drizzle ORM + MySQL)
前端·vue·express·nuxt.js·全栈框架
shmily麻瓜小菜鸡5 天前
vue3里“devDependencies 是开发环境依赖,dependencies 是生产环境依赖”
前端·javascript·vue.js·vscode·vue·angular.js
脾气有点小暴6 天前
ECharts 伪 3D 柱状图完整注释 + 实现原理说明
前端·3d·信息可视化·vue·echarts
sugar__salt6 天前
Vue3 核心特性深度解析:v-model、ref 属性与 nextTick 完全指南
前端·javascript·vue.js·前端框架·vue
勇往直前plus7 天前
Vue3(篇四)单页面应用到路由管理
javascript·typescript·前端框架·vue