Pinia 新手完全指南:从入门到精通的实战教程

目录

[一、为什么选择 Pinia?](#一、为什么选择 Pinia?)

[1. 最大的改进:去掉了冗余的 Mutations](#1. 最大的改进:去掉了冗余的 Mutations)

[2. 原生完美支持 TypeScript](#2. 原生完美支持 TypeScript)

[3. 与组合式 API 无缝集成](#3. 与组合式 API 无缝集成)

[4. 扁平化模块化设计](#4. 扁平化模块化设计)

[5. 轻量高效](#5. 轻量高效)

[Pinia vs Vuex 核心对比](#Pinia vs Vuex 核心对比)

二、核心概念速览

[三、快速上手:5 分钟写第一个 Pinia 例子](#三、快速上手:5 分钟写第一个 Pinia 例子)

[1. 安装 Pinia](#1. 安装 Pinia)

[2. 在 main.js 中注册 Pinia](#2. 在 main.js 中注册 Pinia)

[3. 定义第一个 Store](#3. 定义第一个 Store)

[方式 1:选项式 API(适合 Vue2 转 Vue3 的开发者)](#方式 1:选项式 API(适合 Vue2 转 Vue3 的开发者))

[方式 2:组合式 API(推荐,Vue3 标准写法)](#方式 2:组合式 API(推荐,Vue3 标准写法))

[4. 在组件中使用 Store](#4. 在组件中使用 Store)

[四、核心 API 详解:不同场景的最佳实践](#四、核心 API 详解:不同场景的最佳实践)

[1. State:三种修改方式,分别适合什么场景?](#1. State:三种修改方式,分别适合什么场景?)

[方式 1:直接修改(最简单,适合单个属性修改)](#方式 1:直接修改(最简单,适合单个属性修改))

[方式 2:patch 批量修改(适合同时修改多个属性)](#方式 2:patch 批量修改(适合同时修改多个属性))

[方式 3:通过 Actions 修改(推荐,适合复杂逻辑)](#方式 3:通过 Actions 修改(推荐,适合复杂逻辑))

[2. Getters:派生状态的最佳实践](#2. Getters:派生状态的最佳实践)

基本用法

[带参数的 Getters](#带参数的 Getters)

[3. Actions:同步和异步操作都在这里](#3. Actions:同步和异步操作都在这里)

[同步 Action](#同步 Action)

[异步 Action](#异步 Action)

[调用其他 Action](#调用其他 Action)

五、高级常用特性

[1. 重置 Store 到初始状态](#1. 重置 Store 到初始状态)

[2. 监听状态变化](#2. 监听状态变化)

[3. 状态持久化(最常用插件)](#3. 状态持久化(最常用插件))

安装插件

注册插件

使用持久化

[4. 跨 Store 调用](#4. 跨 Store 调用)

[六、新手最容易踩的 8 个坑](#六、新手最容易踩的 8 个坑)

[1. 直接解构 Store 丢失响应式](#1. 直接解构 Store 丢失响应式)

[2. 忘记注册 Pinia 实例](#2. 忘记注册 Pinia 实例)

[3. 在组合式 Store 中忘记返回属性](#3. 在组合式 Store 中忘记返回属性)

[4. 在 Actions 中使用箭头函数](#4. 在 Actions 中使用箭头函数)

[5. 把所有状态都放在 Pinia 里](#5. 把所有状态都放在 Pinia 里)

[6. 持久化插件配置错误](#6. 持久化插件配置错误)

[7. 多个 Store 循环依赖](#7. 多个 Store 循环依赖)

[8. 不使用 TypeScript](#8. 不使用 TypeScript)

七、最佳实践

[八、什么时候该用 Pinia?什么时候不用?](#八、什么时候该用 Pinia?什么时候不用?)

[应该用 Pinia 的场景](#应该用 Pinia 的场景)

[不应该用 Pinia 的场景](#不应该用 Pinia 的场景)

总结


如果你正在学习 Vue3,那么 Pinia 是你必须掌握的核心技能之一。作为 Vue 官方推荐的新一代状态管理库,Pinia 彻底解决了 Vuex 的诸多痛点,以更简洁的 API、更友好的开发体验和更强大的类型支持,成为了 Vue3 生态中状态管理的标准方案。

本文将从新手的角度出发,由浅入深地讲解 Pinia 的所有核心知识,结合不同场景的实战分析,帮助你快速掌握 Pinia,并能在实际项目中灵活运用。

一、为什么选择 Pinia?

在学习 Pinia 之前,我们首先要明白一个问题:为什么 Vue 官方要推出 Pinia 来替代 Vuex?Pinia 到底好在哪里?

1. 最大的改进:去掉了冗余的 Mutations

这是 Pinia 最具革命性的改变。在 Vuex 中,我们必须通过commit(mutation)来同步修改状态,异步操作必须放在actions中,这导致了大量的样板代码。

而在 Pinia 中,只有 Actions,同步和异步操作都可以在 Actions 中处理,甚至可以直接修改状态。代码量直接减少了 50% 以上。

2. 原生完美支持 TypeScript

Pinia 是用 TypeScript 编写的,开箱即用的类型推断,不需要额外的类型声明。所有属性和方法都有完整的自动补全和类型检查,开发时能及时发现很多潜在错误。

3. 与组合式 API 无缝集成

Pinia 完全是为 Vue3 的组合式 API 设计的,你可以像写普通的 composable 一样定义 Store,完美支持<script setup>语法。

4. 扁平化模块化设计

Pinia 没有 Vuex 那样复杂的嵌套 modules 和命名空间,每个 Store 都是一个独立的模块,通过不同的文件区分。自动代码分割,按需加载,性能更好。

5. 轻量高效

Pinia 的核心代码仅约 1KB(gzip 压缩后),比 Vuex 更轻量,性能也更优秀。同时支持服务端渲染(SSR)和热更新。

Pinia vs Vuex 核心对比

特性 Vuex Pinia
官方状态 维护模式,不再更新 官方推荐,活跃开发
核心概念 State、Getters、Mutations、Actions、Modules State、Getters、Actions
修改状态 必须通过 commit (mutation) 直接修改或通过 actions
TypeScript 支持 需要大量手动类型声明 原生支持,自动类型推断
组合式 API 支持但不友好 原生设计,完美契合
代码量 较多,样板代码多 简洁,减少约 50% 代码

二、核心概念速览

Pinia 的核心概念非常简单,只有四个,新手很容易理解:

  1. Store(仓库):Pinia 的核心,每个 Store 都是一个独立的容器,存储着应用的一部分状态和相关的业务逻辑。
  2. State(状态) :Store 中存储的原始数据,是响应式的,相当于组件中的data
  3. Getters(计算属性) :基于 State 派生出来的状态,有缓存机制,相当于组件中的computed
  4. Actions(动作) :处理业务逻辑的地方,可以是同步或异步操作,相当于组件中的methods

记住:State 负责存储数据,Getters 负责派生数据,Actions 负责修改数据

三、快速上手:5 分钟写第一个 Pinia 例子

1. 安装 Pinia

bash 复制代码
npm install pinia
# 或
yarn add pinia

2. 在 main.js 中注册 Pinia

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

const app = createApp(App)
// 创建Pinia实例并注册到应用
app.use(createPinia())
app.mount('#app')

3. 定义第一个 Store

src目录下创建stores文件夹,然后创建counter.js文件:

Pinia 支持两种定义 Store 的方式:选项式 API组合式 API

方式 1:选项式 API(适合 Vue2 转 Vue3 的开发者)
javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'
// 第一个参数是Store的唯一ID,不能重复
export const useCounterStore = defineStore('counter', {
  // State:存储状态数据,必须是一个函数
  state: () => ({
    count: 0
  }),
  // Getters:计算派生状态
  getters: {
    doubleCount: (state) => state.count * 2
  },
  // Actions:处理业务逻辑
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    }
  }
})
方式 2:组合式 API(推荐,Vue3 标准写法)
javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
  // 相当于state
  const count = ref(0)
  // 相当于getters
  const doubleCount = computed(() => count.value * 2)
  // 相当于actions
  const increment = () => {
    count.value++
  }
  const decrement = () => {
    count.value--
  }
  // 必须返回所有要暴露的属性和方法
  return { count, doubleCount, increment, decrement }
})

4. 在组件中使用 Store

javascript 复制代码
<template>
  <div>
    <p>计数:{{ counter.count }}</p>
    <p>双倍计数:{{ counter.doubleCount }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.decrement">-1</button>
  </div>
</template>
<script setup>
// 导入并实例化Store
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>

就是这么简单!你已经完成了第一个 Pinia 应用。

四、核心 API 详解:不同场景的最佳实践

1. State:三种修改方式,分别适合什么场景?

Pinia 提供了三种修改 State 的方式,不同的场景应该选择不同的方式。

方式 1:直接修改(最简单,适合单个属性修改)
javascript 复制代码
const counter = useCounterStore()
// 直接修改单个属性
counter.count = 10

适用场景:简单的单个属性修改,代码最简洁。

方式 2:$patch 批量修改(适合同时修改多个属性)
javascript 复制代码
const userStore = useUserStore()
// 批量修改多个属性
userStore.$patch({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
})
// 也可以用函数形式,适合修改数组或对象
userStore.$patch((state) => {
  state.hobbies.push('篮球')
  state.age++
})

适用场景:同时修改多个属性,或者修改数组 / 对象的元素。比多次直接修改性能更好。

方式 3:通过 Actions 修改(推荐,适合复杂逻辑)
javascript 复制代码
// 在Store中定义Action
actions: {
  updateUserInfo(newInfo) {
    // 可以在这里添加验证、日志等复杂逻辑
    if (newInfo.age < 0) {
      console.error('年龄不能为负数')
      return
    }
    this.name = newInfo.name
    this.age = newInfo.age
  }
}
// 在组件中调用
userStore.updateUserInfo({ name: '李四', age: 30 })

适用场景:包含复杂逻辑的状态修改,如数据验证、异步操作、日志记录等。这是最推荐的方式,保持代码的可维护性。

2. Getters:派生状态的最佳实践

Getters 是基于 State 派生出来的状态,有缓存机制,只有当依赖的 State 发生变化时才会重新计算。

基本用法
javascript 复制代码
getters: {
  // 接收state作为参数
  adultCount: (state) => {
    return state.users.filter(user => user.age >= 18).length
  },
  // 可以访问其他getter,使用this
  adultAverageAge() {
    const adults = this.users.filter(user => user.age >= 18)
    if (adults.length === 0) return 0
    return adults.reduce((sum, user) => sum + user.age, 0) / adults.length
  }
}
带参数的 Getters

如果需要传递参数,可以返回一个函数:

javascript 复制代码
getters: {
  getUserById: (state) => {
    return (id) => state.users.find(user => user.id === id)
  }
}

在组件中使用:

javascript 复制代码
const user = userStore.getUserById(1)

3. Actions:同步和异步操作都在这里

Actions 是处理业务逻辑的地方,支持同步和异步操作。

同步 Action
javascript 复制代码
actions: {
  increment(num) {
    this.count += num
  }
}
异步 Action
javascript 复制代码
actions: {
  async fetchUserList() {
    // 显示加载状态
    this.loading = true
    try {
      // 调用接口获取数据
      const res = await api.getUserList()
      this.userList = res.data
    } catch (error) {
      console.error('获取用户列表失败', error)
    } finally {
      // 隐藏加载状态
      this.loading = false
    }
  }
}
调用其他 Action

在一个 Action 中可以调用同一个 Store 的其他 Action:

javascript 复制代码
actions: {
  async fetchAndProcessUsers() {
    await this.fetchUserList()
    this.processUserData()
  },
  processUserData() {
    // 处理用户数据
  }
}

五、高级常用特性

1. 重置 Store 到初始状态

一键将 Store 的所有状态重置为初始值,非常适合退出登录时清空用户数据:

javascript 复制代码
const userStore = useUserStore()
userStore.$reset()

2. 监听状态变化

监听整个 Store 的状态变化,可以用来做日志记录、状态持久化等:

javascript 复制代码
userStore.$subscribe((mutation, state) => {
  console.log('状态发生了变化', mutation)
  console.log('最新状态', state)
  // 将状态保存到localStorage
  localStorage.setItem('user', JSON.stringify(state))
})

3. 状态持久化(最常用插件)

使用pinia-plugin-persistedstate插件可以轻松实现状态持久化到 localStorage,不需要手动写代码。

安装插件
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: '',
    username: ''
  }),
  // 开启持久化,自动保存到localStorage
  persist: true
})

4. 跨 Store 调用

在一个 Store 中可以调用其他 Store 的方法:

javascript 复制代码
// stores/user.js
import { useCartStore } from './cart'
export const useUserStore = defineStore('user', {
  actions: {
    async logout() {
      this.token = ''
      this.username = ''
      // 调用购物车Store的清空方法
      const cartStore = useCartStore()
      cartStore.clearCart()
    }
  }
})

六、新手最容易踩的 8 个坑

1. 直接解构 Store 丢失响应式

javascript 复制代码
// 错误:直接解构会丢失响应式
const { count, doubleCount } = useCounterStore()
// 正确:使用storeToRefs保持响应式
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(useCounterStore())

注意:方法不需要用 storeToRefs,直接解构即可:

javascript 复制代码
const { increment, decrement } = useCounterStore()

2. 忘记注册 Pinia 实例

如果在 main.js 中忘记注册 Pinia,会报错:"getActivePinia()" was called but there was no active Pinia

3. 在组合式 Store 中忘记返回属性

组合式 Store 必须在最后返回所有要暴露的属性和方法,否则组件中无法访问。

4. 在 Actions 中使用箭头函数

javascript 复制代码
// 错误:箭头函数会导致this指向错误
actions: {
  increment: () => {
    this.count++ // this是undefined
  }
}
// 正确:使用普通函数
actions: {
  increment() {
    this.count++
  }
}

5. 把所有状态都放在 Pinia 里

Pinia 是用来管理全局共享状态 的,不是所有状态都要放在 Pinia 里。组件内部的局部状态应该用refreactive管理。

6. 持久化插件配置错误

如果开启了持久化但数据没有保存,检查是否在 main.js 中正确注册了插件,以及是否在 Store 中设置了persist: true

7. 多个 Store 循环依赖

避免在 Store A 中导入 Store B,同时在 Store B 中导入 Store A,这会导致循环依赖错误。

8. 不使用 TypeScript

Pinia 对 TypeScript 的支持非常好,使用 TypeScript 可以获得完整的类型检查和自动补全,大幅提升开发效率和代码质量。

七、最佳实践

  1. 按功能模块划分 Store :不要把所有状态都放在一个 Store 里,一个业务模块对应一个 Store(如userStorecartStoreorderStore)。
  2. 复杂逻辑放在 Actions 中:组件只负责调用 actions,不要在组件中写复杂的业务逻辑。
  3. 使用 Getters 派生状态:避免在组件中重复计算相同的派生状态。
  4. 合理使用状态持久化:只保存需要持久化的数据(如 token、用户信息),不要保存所有状态。
  5. 保持 Store 的纯粹性:不要在 Store 中操作 DOM 或路由,保持 Store 只负责状态管理。
  6. 配合 TypeScript 使用:充分利用 Pinia 的类型系统,减少运行时错误。

八、什么时候该用 Pinia?什么时候不用?

应该用 Pinia 的场景

  • 多个组件共享的全局状态(如用户信息、购物车、主题设置)
  • 需要在不同页面之间传递的数据
  • 复杂的业务逻辑需要统一管理
  • 需要持久化到本地的数据

不应该用 Pinia 的场景

  • 组件内部的局部状态
  • 父子组件之间传递的数据(用 Props 和事件即可)
  • 临时的、不需要共享的数据

总结

Pinia 是 Vue3 时代状态管理的最佳选择,它继承了 Vuex 的优点,同时解决了 Vuex 的诸多痛点。对于新手来说,Pinia 的学习曲线非常平缓,只要你掌握了 Vue3 的基础,就能很快上手。

记住:Pinia 的核心思想是简单、直观、高效。不要把简单的事情复杂化,根据实际场景选择合适的用法,才能发挥 Pinia 的最大优势。

现在,打开你的编辑器,动手写第一个 Pinia 应用吧!实践是最好的老师。

相关推荐
向日的葵0061 小时前
快速了解vue中的路由如何实现(路由一)
前端·vue.js·vue·路由
珎珎啊1 小时前
React 和 Vue 3的区别
前端·vue.js·react.js
shadow_glory1 小时前
vue3自定义指令directives
前端·javascript·vue.js
Front思1 小时前
如何学习Shopify前端开发?
前端·学习
码云骑士1 小时前
语音合成演示 - Web Speech API
前端
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_50:(传统布局方法与网格系统)
前端·css·ui·tensorflow·媒体
薛先生_0991 小时前
vue-路由模块封装
前端·javascript·vue.js
薛先生_0991 小时前
vue-router-link实现导航高亮效果
前端·javascript·vue.js
郑州光合科技余经理1 小时前
海外版外卖系统源码:支付/地图/多语言核心代码实现
android·java·前端·后端·架构·uni-app·php