05-Pinia状态管理
Pinia 状态管理
1. 什么是 Pinia
Pinia 是 Vue 的新一代状态管理库,由 Vue 团队开发。它提供了更简洁的 API、更好的 TypeScript 支持和开发工具集成。
Pinia 的优势
- 完整的 TypeScript 支持
- 无需 mutations,只有 state、getters、actions
- 支持 Vue 2 和 Vue 3
- 自动代码分割
- 插件系统支持扩展
- DevTools 集成
2. 安装与配置
安装
npm install pinia
创建 Store
// stores/index.js import { createPinia } from 'pinia'const pinia = createPinia()
export default pinia
在应用中使用
// main.js import { createApp } from 'vue' import App from './App.vue' import pinia from './stores'
const app = createApp(App)
app.use(pinia)
app.mount('#app')
3. 定义 Store
Option Store 风格
// stores/counter.js import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {
// State
state: () => {
return {
count: 0,
name: '计数器'
}
},// Getters
getters: {
doubleCount: (state) => state.count * 2,// 接受参数的 getter
countPlus: (state) => {
return (num) => state.count + num
},// 使用其他 getter
doubleCountPlusOne() {
return this.doubleCount + 1
}
},// Actions
actions: {
increment() {
this.count++
},decrement() {
this.count--
},// 异步操作
async fetchCount() {
const response = await fetch('/api/count')
const data = await response.json()
this.count = data.count
},
// 接受参数
incrementBy(amount) {
this.count += amount
}
}
})
Setup Store 风格
// stores/counter.js import { ref, computed } from 'vue' import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
const name = ref('计数器')// Getters
const doubleCount = computed(() => count.value * 2)// Actions
function increment() {
count.value++
}function decrement() {
count.value--
}async function fetchCount() {
const response = await fetch('/api/count')
const data = await response.json()
count.value = data.count
}
// 返回需要暴露的内容
return {
count,
name,
doubleCount,
increment,
decrement,
fetchCount
}
})
4. 使用 Store
在组件中使用
<script setup> import { useCounterStore } from '@/stores/counter'const counterStore = useCounterStore()
// 访问 state
console.log(counterStore.count)// 访问 getter
console.log(counterStore.doubleCount)// 调用 action
counterStore.increment()
counterStore.incrementBy(5)
</script>
<template>
<div>
<p>计数: {{ counterStore.count }}</p>
<p>双倍: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
</div>
</template>
使用 storeToRefs 保持响应性
<script setup> import { storeToRefs } from 'pinia' import { useCounterStore } from '@/stores/counter'const counterStore = useCounterStore()
// 解构时保持响应性
const { count, doubleCount } = storeToRefs(counterStore)// actions 不需要 storeToRefs
const { increment, decrement } = counterStore
</script>
<template>
<div>
<p>计数: {{ count }}</p>
<p>双倍: {{ doubleCount }}</p>
<button @click="increment">增加</button>
</div>
</template>
5. State 操作
读取 State
const store = useCounterStore()// 直接访问
console.log(store.count)
// 解构(需要 storeToRefs)
const { count } = storeToRefs(store)
修改 State
const store = useCounterStore()// 方式一:直接修改
store.count++// 方式二:通过 action
store.increment()// 方式三:$patch 对象形式
store.$patch({
count: 10,
name: '新名称'
})// 方式四:$patch 函数形式(适合数组操作)
store.$patch((state) => {
state.count++
state.name = '修改后的名称'
state.items.push({ id: 1, name: '新项' })
})
// 方式五:替换整个 state
store.$state = {
count: 0,
name: '重置'
}
重置 State
// 重置到初始值
store.$reset()
6. Getters
基础 Getter
export const useTodoStore = defineStore('todo', { state: () => ({ todos: [ { id: 1, text: '学习 Vue', done: false }, { id: 2, text: '学习 Pinia', done: true } ] }),getters: {
// 简单 getter
finishedTodos: (state) => {
return state.todos.filter(todo => todo.done)
},// 计算属性
unfinishedCount: (state) => {
return state.todos.filter(todo => !todo.done).length
},
// 访问其他 getter
summary() {
return${this.finishedTodos.length} 已完成,${this.unfinishedCount} 未完成
}
}
})
接受参数的 Getter
getters: { // 返回一个函数 getTodoById: (state) => { return (id) => state.todos.find(todo => todo.id === id) } }
// 使用
const todo = store.getTodoById(1)
7. Actions
同步 Action
actions: { addTodo(text) { this.todos.push({ id: Date.now(), text, done: false }) },toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
if (todo) {
todo.done = !todo.done
}
},removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id)
},
// 调用其他 action
resetTodos() {
this.todos = []
this.addTodo('默认任务')
}
}
异步 Action
actions: { async fetchTodos() { try { const response = await fetch('/api/todos') const data = await response.json() this.todos = data } catch (error) { console.error('获取任务失败:', error) } },
async addTodoAsync(text) {
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
})
const newTodo = await response.json()
this.todos.push(newTodo)
} catch (error) {
console.error('添加任务失败:', error)
}
}
}
8. Store 订阅
订阅 State 变化
const store = useCounterStore()// 监听 state 变化
store.$subscribe((mutation, state) => {
console.log('变化类型:', mutation.type) // direct, patch object, patch function
console.log('store id:', mutation.storeId)
console.log('当前 state:', state)
// 可用于持久化到 localStorage
localStorage.setItem('counter', JSON.stringify(state))
}, { detached: true })
订阅 Action 调用
const store = useCounterStore()store.$onAction(({ name, store, args, after, onError }) => {
console.log(Action "${name}" 被调用,参数:, args)after((result) => {
console.log(Action "${name}" 完成,结果:, result)
})
onError((error) => {
console.error(Action "${name}" 出错:, error)
})
})
9. Store 组合
// stores/user.js export const useUserStore = defineStore('user', { state: () => ({ token: null, userInfo: null }), actions: { async login(credentials) { // ... } } })// stores/cart.js
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),getters: {
// 使用其他 store
totalPrice: (state) => {
const userStore = useUserStore()// 根据用户等级计算折扣
if (userStore.userInfo?.vip) {
return state.items.reduce((sum, item) => sum + item.price * 0.8, 0)
}
return state.items.reduce((sum, item) => sum + item.price, 0)
}
}
})
10. 持久化存储
使用 pinia-plugin-persistedstate
npm install pinia-plugin-persistedstate
// main.js import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// stores/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
// 开启持久化
persist: true
// 或者自定义配置
// persist: {
// key: 'my-counter',
// storage: localStorage,
// paths: ['count']
// }
})
11. 完整示例:Todo 应用
// stores/todo.js import { defineStore } from 'pinia'export const useTodoStore = defineStore('todo', {
state: () => ({
todos: [],
filter: 'all' // all, active, completed
}),getters: {
filteredTodos() {
switch (this.filter) {
case 'active':
return this.todos.filter(t => !t.done)
case 'completed':
return this.todos.filter(t => t.done)
default:
return this.todos
}
},activeCount: (state) => {
return state.todos.filter(t => !t.done).length
},completedCount: (state) => {
return state.todos.filter(t => t.done).length
}
},actions: {
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
done: false,
createdAt: new Date()
})
},toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
if (todo) todo.done = !todo.done
},removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id)
},clearCompleted() {
this.todos = this.todos.filter(t => !t.done)
},setFilter(filter) {
this.filter = filter
},async loadTodos() {
try {
const response = await fetch('/api/todos')
this.todos = await response.json()
} catch (error) {
console.error('加载失败:', error)
}
},async saveTodos() {
try {
await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.todos)
})
} catch (error) {
console.error('保存失败:', error)
}
}
},
persist: true
})
<!-- TodoApp.vue -->
<script setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useTodoStore } from '@/stores/todo'
const todoStore = useTodoStore()
const { filteredTodos, activeCount, completedCount } = storeToRefs(todoStore)
const newTodo = ref('')
function addTodo() {
if (newTodo.value.trim()) {
todoStore.addTodo(newTodo.value.trim())
newTodo.value = ''
}
}
</script>
<template>
<div class="todo-app">
<h1>待办事项</h1>
<!-- 添加 -->
<form @submit.prevent="addTodo">
<input v-model="newTodo" placeholder="添加新任务..." />
<button type="submit">添加</button>
</form>
<!-- 筛选 -->
<div class="filters">
<button @click="todoStore.setFilter('all')">全部</button>
<button @click="todoStore.setFilter('active')">进行中</button>
<button @click="todoStore.setFilter('completed')">已完成</button>
</div>
<!-- 列表 -->
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" :checked="todo.done" @change="todoStore.toggleTodo(todo.id)" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="todoStore.removeTodo(todo.id)">删除</button>
</li>
</ul>
<!-- 统计 -->
<footer>
<span>{{ activeCount }} 项未完成</span>
<button @click="todoStore.clearCompleted" v-show="completedCount">清除已完成</button>
</footer>
</div>
</template>
小结
Pinia 是 Vue 3 推荐的状态管理库:
- 定义 Store:使用 defineStore,支持 Option 和 Setup 两种风格
- State:存储应用状态,支持响应式修改
- Getters:计算派生状态,类似 computed
- Actions:修改 state 的方法,支持异步操作
- 订阅:监听 state 和 action 变化
- 组合:store 之间可以相互引用
- 持久化:通过插件轻松实现数据持久化

浙公网安备 33010602011771号