Vue3 Pinia 从入门到精通

Vue3 Pinia 从入门到精通

一、Pinia 简介与核心优势

Pinia 是 Vue 官方推荐的新一代状态管理库,由 Vuex 核心团队开发,旨在替代 Vuex 成为 Vue 3 生态的首选状态管理方案。Pinia 于 2021 年正式发布,目前已成为 Vue 3 项目的标配状态管理工具。

核心优势对比

特性 Pinia Vuex 3 (Vue 2) Vuex 4 (Vue 3)
包体积 ~1KB (gzip) ~10KB ~5KB
TypeScript 支持 原生完美支持 需额外类型声明 部分支持
核心概念 State, Getters, Actions State, Getters, Mutations, Actions, Modules 同左
模块化 天然支持多 Store,扁平化结构 需通过 Modules 嵌套,需命名空间 同左
DevTools 支持 完整支持,含时间旅行 支持 支持 Vue 3,但体验一般
代码分割 自动支持,按需加载 需手动配置 需手动配置

与 Vuex 的核心差异

  1. 无 Mutations:Pinia 移除了 Vuex 中冗余的 Mutations,同步/异步操作统一由 Actions 处理,减少 40% 样板代码。
javascript 复制代码
// Vuex
store.commit('increment', 1)  // 同步
store.dispatch('fetchData')   // 异步

// Pinia
store.increment(1)  // 同步
store.fetchData()   // 异步(无需额外分层)
  1. 更简洁的 API:Pinia 采用更直观的 API 设计,减少概念负担。

  2. TypeScript 优先设计:所有 API 均类型安全,自动推导状态类型,无需手动声明。

  3. 扁平化模块结构:每个 Store 独立管理,避免命名空间冲突,模块间通信更直观。

二、Pinia 安装与基础配置

1. 安装 Pinia

bash 复制代码
# 使用 npm
npm install pinia

# 使用 yarn
yarn add pinia

2. 初始化 Pinia

在入口文件(main.jsmain.ts)中创建并挂载 Pinia 实例:

javascript 复制代码
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)  // 挂载 Pinia
app.mount('#app')

3. 创建第一个 Store

src/stores 目录下创建 Store 文件(推荐按功能模块划分):

javascript 复制代码
// src/stores/counter.js
import { defineStore } from 'pinia'

// 定义 Store,第一个参数是唯一 ID(必填)
export const useCounterStore = defineStore('counter', {
  // 状态(类似组件的 data)
  state: () => ({
    count: 0,
    name: 'Eduardo'
  }),
  
  // 计算属性(类似组件的 computed)
  getters: {
    doubleCount: (state) => state.count * 2,
    // 访问其他 getters
    doubleCountPlusOne() {
      return this.doubleCount + 1
    }
  },
  
  // 方法(类似组件的 methods,支持同步/异步)
  actions: {
    increment() {
      this.count++
    },
    async fetchData() {
      // 模拟异步请求
      const data = await new Promise(resolve => 
        setTimeout(() => resolve(10), 1000)
      )
      this.count = data
    }
  }
})

4. 在组件中使用 Store

vue 复制代码
<!-- CounterComponent.vue -->
<template>
  <div>
    <h2>Count: {{ counter.count }}</h2>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.fetchData">Async Update</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

// 获取 Store 实例
const counter = useCounterStore()
</script>

三、核心概念详解

1. State(状态)

State 是 Store 的数据源,使用 state() 函数返回初始状态对象:

javascript 复制代码
state: () => ({
  user: null,
  token: '',
  permissions: []
})

修改状态

  • 直接修改(推荐):store.count++

  • 批量修改:store.$patch({ count: 10, name: 'New Name' })

  • 函数式修改:

    javascript 复制代码
    store.$patch(state => {
      state.items.push({ id: 1, name: 'New Item' })
      state.total++
    })

重置状态store.$reset()

2. Getters(计算属性)

Getters 用于派生出基于 State 的新状态,结果会自动缓存:

javascript 复制代码
getters: {
  // 基础用法
  filteredPermissions: (state) => state.permissions.filter(p => p.enabled),
  
  // 带参数的 getter
  hasPermission: (state) => (permission) => 
    state.permissions.includes(permission),
  
  // 访问其他 getter
  permissionCount(state, getters) {
    return getters.filteredPermissions.length
  }
}

3. Actions(方法)

Actions 用于封装业务逻辑,支持同步和异步操作:

javascript 复制代码
actions: {
  // 同步 action
  setUser(user) {
    this.user = user
    this.token = user.token
  },
  
  // 异步 action
  async login(credentials) {
    this.loading = true
    try {
      const response = await api.login(credentials)
      this.setUser(response.data)  // 调用其他 action
      return response.data
    } catch (error) {
      this.error = error.message
      throw error  // 允许组件捕获错误
    } finally {
      this.loading = false
    }
  }
}

四、高级用法

1. Setup 函数式写法(推荐)

Pinia 支持更灵活的组合式 API 风格定义 Store:

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

export const useUserStore = defineStore('user', () => {
  // 状态(对应 state)
  const user = ref(null)
  const token = ref('')
  
  // 计算属性(对应 getters)
  const isLoggedIn = computed(() => !!token.value)
  
  // 方法(对应 actions)
  function setUser(userData) {
    user.value = userData
    token.value = userData.token
  }
  
  async function login(credentials) {
    const response = await api.login(credentials)
    setUser(response.data)
  }
  
  // 暴露状态和方法
  return { user, token, isLoggedIn, setUser, login }
})

2. 模块化管理

Pinia 推荐按业务域拆分多个独立 Store:

复制代码
src/
└── stores/
    ├── user.js       # 用户模块
    ├── cart.js       # 购物车模块
    └── product.js    # 产品模块

跨 Store 通信

javascript 复制代码
// stores/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', {
  actions: {
    checkout() {
      const userStore = useUserStore()
      if (!userStore.isLoggedIn) {
        throw new Error('请先登录')
      }
      // 执行结账逻辑
    }
  }
})

3. 状态持久化

使用 pinia-plugin-persistedstate 插件实现状态持久化:

bash 复制代码
npm install pinia-plugin-persistedstate
javascript 复制代码
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)  // 注册插件

基本用法

javascript 复制代码
// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({ token: '', userInfo: {} }),
  persist: true  // 开启持久化(默认存储到 localStorage)
})

高级配置

javascript 复制代码
persist: {
  key: 'user-store',          // 存储键名
  storage: sessionStorage,   // 使用 sessionStorage
  paths: ['token'],          // 只持久化 token 字段
  // 自定义序列化(如加密)
  serializer: {
    serialize: (data) => encrypt(JSON.stringify(data)),
    deserialize: (data) => JSON.parse(decrypt(data))
  }
}

4. 响应式处理与解构

直接解构 Store 会丢失响应性,需使用 storeToRefs

javascript 复制代码
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const counter = useCounterStore()

// 错误:解构后失去响应性
const { count, doubleCount } = counter  // ❌

// 正确:使用 storeToRefs 保持响应性
const { count, doubleCount } = storeToRefs(counter)  // ✅

// Actions 可直接解构(它们是绑定到 Store 的函数)
const { increment } = counter  // ✅
</script>

五、TypeScript 深度集成

Pinia 为 TypeScript 提供一流支持,所有状态和方法自动推导类型:

1. 类型化 State

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

// 定义用户类型
interface User {
  id: number
  name: string
  role: 'admin' | 'editor' | 'viewer'
}

export const useUserStore = defineStore('user', {
  state: (): { 
    currentUser: User | null,
    permissions: string[]
  } => ({
    currentUser: null,
    permissions: []
  }),
  
  getters: {
    isAdmin: (state): boolean => 
      state.currentUser?.role === 'admin' || false
  },
  
  actions: {
    setUser(user: User) {  // 参数类型约束
      this.currentUser = user
    }
  }
})

2. 组合式 Store 类型化

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

interface Product {
  id: number
  name: string
  price: number
}

export const useProductStore = defineStore('product', () => {
  const products = ref<Product[]>([])
  
  const filteredProducts = computed(() => 
    products.value.filter(p => p.price < 100)
  )
  
  function addProduct(product: Product) {  // 类型约束
    products.value.push(product)
  }
  
  return { products, filteredProducts, addProduct }
})

六、与 Vue Router 结合使用

1. 在 Store 中使用路由

javascript 复制代码
// stores/auth.js
import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'

export const useAuthStore = defineStore('auth', {
  actions: {
    login(credentials) {
      const router = useRouter()
      // 登录逻辑...
      router.push('/dashboard')  // 登录后跳转
    },
    
    logout() {
      const router = useRouter()
      // 登出逻辑...
      router.push('/login')  // 登出后跳转
    }
  }
})

2. 路由守卫中使用 Store

javascript 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { 
      path: '/dashboard', 
      component: Dashboard,
      meta: { requiresAuth: true }
    }
  ]
})

router.beforeEach((to, from, next) => {
  const authStore = useAuthStore()
  
  if (to.meta.requiresAuth && !authStore.isLoggedIn) {
    next('/login')  // 未登录则重定向到登录页
  } else {
    next()
  }
})

七、最佳实践与性能优化

1. 状态设计原则

  • 扁平化状态:避免深层嵌套,便于访问和持久化

    javascript 复制代码
    // 不推荐
    state: () => ({
      user: { info: { name: '', age: 0 } }
    })
    
    // 推荐
    state: () => ({
      userName: '',
      userAge: 0
    })
  • 最小化全局状态:仅将跨组件共享的数据放入 Store,局部状态留在组件内。

2. 性能优化技巧

  • 批量更新状态 :使用 $patch 减少响应式触发次数

    javascript 复制代码
    // 优化前(触发 2 次更新)
    store.name = 'New Name'
    store.count = 10
    
    // 优化后(仅触发 1 次更新)
    store.$patch({
      name: 'New Name',
      count: 10
    })
  • 缓存计算结果:复杂计算逻辑使用 Getters,利用其缓存特性

  • 按需加载大型 Store

    javascript 复制代码
    // 路由懒加载时加载 Store
    const ProductDetail = () => import('@/views/ProductDetail.vue')
    // 在组件中动态导入 Store
    const useLargeStore = await import('@/stores/largeStore').then(m => m.useLargeStore())

3. 项目结构推荐

复制代码
src/
├── stores/                # Store 目录
│   ├── index.ts           # 统一导出所有 Store
│   ├── user.ts            # 用户模块
│   ├── cart.ts            # 购物车模块
│   └── modules/           # 子模块(如需)
│       └── product.ts     # 产品子模块
├── router/                # 路由配置
├── views/                 # 页面组件
└── components/            # 通用组件

八、常见问题解决方案

1. "getActivePinia was called with no active Pinia" 错误

原因 :在 Pinia 实例创建前调用了 useStore()

解决方案 :确保 app.use(pinia) 在任何 useStore() 调用前执行。

2. 持久化状态未生效

检查清单

  • 是否正确注册 pinia-plugin-persistedstate 插件
  • 确认浏览器隐私模式未禁用存储
  • 复杂对象(如 Date)需自定义序列化

3. TypeScript 类型推断失败

解决方案

  • 为 State 显式声明接口
  • 使用组合式 Store 时,确保所有状态都通过 ref/reactive 定义

4. 开发环境热更新失效

解决方案

javascript 复制代码
// 在 Store 文件末尾添加
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
}

九、总结与迁移指南

Pinia 作为 Vue 3 官方推荐的状态管理库,凭借其简洁的 API、出色的 TypeScript 支持和灵活的模块化设计,已成为现代 Vue 项目的首选方案。对于现有 Vuex 项目,可按以下步骤迁移:

  1. 安装 Pinia 并创建基础 Store
  2. 逐步迁移:按模块将 Vuex Store 转换为 Pinia Store
  3. 替换 Mutations :将 commit 调用改为直接修改状态
  4. 优化模块化:将嵌套 Modules 拆分为独立 Pinia Store
  5. 启用持久化 :替换 vuex-persistedstate 为 Pinia 持久化插件

通过本文的学习,你已掌握 Pinia 的核心概念、高级用法和最佳实践。Pinia 的设计哲学是"让状态管理像使用普通变量一样简单",这种简洁性和强大功能的平衡,使其成为 Vue 3 生态中不可或缺的一部分。

相关推荐
syt_10131 小时前
设计模式之-工厂模式
javascript·单例模式·设计模式
卡布叻_星星2 小时前
Docker之Nginx前端部署(Windows版-x86_64(AMD64)-离线)
前端·windows·nginx
LYFlied2 小时前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
weibkreuz2 小时前
React的基本使用@2
前端·javascript·react.js
于是我说2 小时前
前端JavaScript 项目中 获取当前页面滚动位置
开发语言·前端·javascript
小肖爱笑不爱笑2 小时前
JavaScript
java·javascript·json·web
GISer_Jing2 小时前
AI在前端开发&营销领域应用
前端·aigc·音视频
凯小默2 小时前
02.内存管理和内存泄漏
javascript
Hao_Harrision2 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨ | DragNDrop(拖拽占用组件)
前端·react.js·typescript·tailwindcss·vite7