前端学习教程-Pinia 教程
Pinia 是 Vue 官方推荐的状态管理库,替代了传统的 Vuex,专为 Vue3 设计,支持 Composition API 和 TypeScript,API 更简洁,是目前 Vue 项目状态管理的最佳选择。
一、为什么选择 Pinia?
与 Vuex 相比,Pinia 具有以下优势:
- 无需
mutations,直接在actions中修改状态 - 完美支持 Vue3 的 Composition API
- 内置 TypeScript 支持,类型推断更友好
- 简化的 API,学习成本更低
- 支持多个 Store 实例,无需嵌套模块
- 更轻量(约 1KB)
二、安装 Pinia
在 Vue3 项目中安装 Pinia:
npm install pinia --save
# 或
yarn add pinia
三、初始化 Pinia
首先需要在项目入口文件中创建 Pinia 实例并挂载到 Vue 应用(main.js):
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia' // 导入 createPinia
// 创建 Pinia 实例
const pinia = createPinia()
// 创建应用并使用 Pinia
createApp(App)
.use(pinia) // 挂载 Pinia
.mount('#app')
四、核心概念与基础使用
1. 创建第一个 Store
Store 是 Pinia 的核心,用于存储全局状态。使用 defineStore() 创建 Store,它接收两个参数:
- 第一个参数:Store 的唯一 ID(字符串)
- 第二个参数:配置对象(包含
state、getters、actions)
import { defineStore } from 'pinia'
// 定义并导出 Store
export const useCounterStore = defineStore('counter', {
// 状态:存储数据的地方
state: () => ({
count: 0,
name: '计数器'
}),
// 计算属性:基于 state 派生的数据
getters: {
// 基本使用
doubleCount: (state) => state.count * 2,
// 使用其他 getters(通过 this)
doubleCountPlusOne() {
return this.doubleCount + 1
},
// 带参数的 getter(返回函数)
getCountWithAdd: (state) => (num) => state.count + num
},
// 方法:处理业务逻辑(支持同步和异步)
actions: {
increment() {
// 直接修改 state(无需 mutations)
this.count++
},
decrement() {
this.count--
},
// 带参数的 action
incrementBy(num) {
this.count += num
},
// 异步 action(如请求 API)
async fetchData() {
const res = await fetch('https://api.example.com/count')
const data = await res.json()
this.count = data.count
}
}
})
2. 在组件中使用 Store
在组件中通过创建的 useCounterStore 函数获取 Store 实例,然后访问其 state、getters 和 actions。
(1)使用 Composition API(推荐)
<template>
<div class="counter">
<h2>{{ counterStore.name }}</h2>
<p>当前计数:{{ counterStore.count }}</p>
<p>计数翻倍:{{ counterStore.doubleCount }}</p>
<p>计数翻倍+1:{{ counterStore.doubleCountPlusOne }}</p>
<p>计数+5:{{ counterStore.getCountWithAdd(5) }}</p>
<button @click="counterStore.increment">+1</button>
<button @click="counterStore.decrement">-1</button>
<button @click="counterStore.incrementBy(10)">+10</button>
<button @click="counterStore.fetchData">从API获取数据</button>
</div>
</template>
<script setup>
// 导入 Store
import { useCounterStore } from '../stores/counterStore'
// 获取 Store 实例(注意:不要解构,会失去响应性)
const counterStore = useCounterStore()
</script>
(2)使用 Options API
如果使用 Options API,可以通过 mapState、mapGetters、mapActions 辅助函数映射 Store:
<template>
<div>
<p>计数:{{ count }}</p>
<p>翻倍:{{ doubleCount }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { useCounterStore } from '../stores/counterStore'
import { mapState, mapGetters, mapActions } from 'pinia'
export default {
computed: {
// 映射 state
...mapState(useCounterStore, ['count', 'name']),
// 映射 getters
...mapGetters(useCounterStore, ['doubleCount'])
},
methods: {
// 映射 actions
...mapActions(useCounterStore, ['increment', 'decrement'])
}
}
</script>
五、修改 State 的多种方式
Pinia 允许通过多种方式修改 state,比 Vuex 更灵活:
1. 直接修改(最简单)
const counterStore = useCounterStore()
counterStore.count++ // 直接修改
counterStore.name = '新计数器' // 直接修改
2. 使用 $patch 批量修改
适合同时修改多个状态:
// 对象形式
counterStore.$patch({
count: 10,
name: '批量修改后的计数器'
})
// 函数形式(更灵活,支持复杂逻辑)
counterStore.$patch((state) => {
state.count += 5
state.name = '函数修改的计数器'
})
3. 通过 actions 修改
适合包含业务逻辑的修改(推荐):
// 在 Store 中定义 action
actions: {
resetAndSet(num) {
this.count = 0 // 重置
this.count += num // 再修改
this.name = '通过action修改'
}
}
// 在组件中调用
counterStore.resetAndSet(10)
4. 替换整个 State
使用 $state 替换整个状态对象:
counterStore.$state = {
count: 100,
name: '全新状态'
}
六、异步 Actions 详解
Pinia 的 actions 支持异步操作(如 API 请求),无需像 Vuex 那样区分 actions 和 mutations:
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
loading: false,
error: null
}),
actions: {
// 登录(异步操作)
async login(username, password) {
try {
this.loading = true
this.error = null
// 调用 API
const res = await axios.post('/api/login', {
username,
password
})
// 存储用户信息
this.userInfo = res.data.user
// 保存 token 到本地存储
localStorage.setItem('token', res.data.token)
return res.data // 返回结果给组件
} catch (err) {
this.error = err.response?.data?.message || '登录失败'
throw err // 抛出错误,让组件可以捕获
} finally {
this.loading = false // 无论成功失败,都结束加载
}
},
// 退出登录
logout() {
this.userInfo = null
localStorage.removeItem('token')
},
// 获取用户信息
async fetchUserInfo() {
try {
this.loading = true
const token = localStorage.getItem('token')
const res = await axios.get('/api/user', {
headers: { Authorization: `Bearer ${token}` }
})
this.userInfo = res.data
} catch (err) {
this.error = '获取用户信息失败'
} finally {
this.loading = false
}
}
}
})
在组件中使用异步 action:
<script setup>
import { useUserStore } from '../stores/userStore'
const userStore = useUserStore()
const handleLogin = async () => {
try {
await userStore.login('admin', '123456')
alert('登录成功')
} catch (err) {
alert(userStore.error) // 显示错误信息
}
}
</script>
七、Store 组合(跨 Store 调用)
在大型项目中,可能需要多个 Store 协同工作,Pinia 支持在一个 Store 中直接使用另一个 Store:
import { defineStore } from 'pinia'
import { useUserStore } from './userStore' // 导入其他 Store
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
totalPrice: (state) => {
return state.items.reduce((total, item) => total + item.price * item.quantity, 0)
},
// 结合用户信息计算折扣价
discountedPrice() {
const userStore = useUserStore() // 获取用户 Store
// 会员打9折
return userStore.userInfo?.isVip ? this.totalPrice * 0.9 : this.totalPrice
}
},
actions: {
addItem(product) {
const userStore = useUserStore()
// 未登录不能添加到购物车
if (!userStore.userInfo) {
throw new Error('请先登录')
}
this.items.push({ ...product, quantity: 1 })
}
}
})
八、持久化存储(状态持久化)
Pinia 本身不提供持久化功能,但可以通过 pinia-plugin-persistedstate 插件实现状态持久化(刷新页面后状态不丢失):
1. 安装插件
npm install pinia-plugin-persistedstate --save
2. 配置插件
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 导入插件
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 使用插件
createApp(App).use(pinia).mount('#app')
3. 在 Store 中启用持久化
export const useUserStore = defineStore('user', {
state: () => ({ /* ... */ }),
// 启用持久化
persist: true, // 默认为 localStorage 存储整个 state
// 或自定义配置
persist: {
key: 'user-storage', // 存储的键名
storage: sessionStorage, // 使用 sessionStorage
paths: ['userInfo'] // 只持久化 userInfo 字段
}
})
九、最佳实践
- 按功能拆分 Store:避免创建单一的大 Store,按功能(如
userStore、cartStore、productStore)拆分 - 使用 Composition API:在组件中优先使用
setup()语法,更符合 Vue3 生态 - 复杂逻辑放 actions:组件只负责调用 actions,业务逻辑封装在 Store 中
- 避免过度使用全局状态:只有多个组件共享的数据才需要放入 Store,组件内部数据用
ref/reactive - TypeScript 类型定义:为 state 和 actions 参数添加类型,提升开发体验
总结
Pinia 是 Vue3 状态管理的最佳选择,其简洁的 API 和对 Composition API 的完美支持,让状态管理变得简单直观。通过本教程,你已掌握:
- Pinia 的安装与初始化
- Store 的创建(state、getters、actions)
- 状态修改的多种方式
- 异步 actions 的实现
- Store 组合与持久化存储

浙公网安备 33010602011771号