Pinia
什么是 Pinia?
Pinia 是 Vue 3 官方推荐的状态管理库,它替代了 Vuex,提供了更简洁的 API、更好的 TypeScript 支持和更灵活的结构。Pinia 支持 Vue 2 和 Vue 3,并且可以与组合式 API 无缝集成。
1. 安装 Pinia
使用 npm 或 yarn 安装 Pinia。
示例:安装 Pinia
# 使用 npm
npm install pinia
# 使用 yarn
yarn add pinia
2. 基本配置
创建 Pinia 实例并在应用中使用。
示例:基本配置
// 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)
app.mount('#app')
3. 创建 Store
使用 defineStore() 函数创建一个 store。
示例:创建 Store
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
name: 'Vue3'
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
// 使用其他 getter
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// 方法
actions: {
increment() {
this.count++
},
// 异步操作
async fetchData() {
const response = await fetch('https://api.example.com/data')
const data = await response.json()
this.name = data.name
}
}
})
4. 使用 Store
在组件中使用 store。
示例:在组件中使用 Store
<template>
<div class="counter">
<h2>计数器: {{ counter.count }}</h2>
<h3>双倍计数: {{ counter.doubleCount }}</h3>
<h3>双倍计数 + 1: {{ counter.doubleCountPlusOne }}</h3>
<h3>名称: {{ counter.name }}</h3>
<button @click="counter.increment">增加</button>
<button @click="counter.fetchData">获取数据</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia'
// 创建 store 实例
const counter = useCounterStore()
// 使用 storeToRefs 将状态转换为响应式引用
const { count, name } = storeToRefs(counter)
// 重置状态
function reset() {
counter.$reset()
}
</script>
5. 修改状态
Pinia 提供了多种修改状态的方式:
5.1 直接修改
示例:直接修改状态
const counter = useCounterStore()
counter.count++
counter.name = 'New Name'
5.2 使用 $patch 方法
使用 $patch 方法可以同时修改多个状态。
示例:使用 $patch 方法
const counter = useCounterStore()
// 对象形式
counter.$patch({
count: counter.count + 1,
name: 'New Name'
})
// 函数形式
counter.$patch((state) => {
state.count++
state.name = 'New Name'
})
5.3 使用 actions
对于复杂的状态修改逻辑,建议使用 actions。
示例:使用 actions
// stores/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment(amount = 1) {
this.count += amount
},
async incrementAsync(amount = 1) {
await new Promise(resolve => setTimeout(resolve, 1000))
this.count += amount
}
}
})
// 在组件中使用
const counter = useCounterStore()
counter.increment(5) // 增加 5
counter.incrementAsync(10) // 异步增加 10
6. Getters
Getters 用于计算派生状态,类似于组件中的 computed 属性。
示例:Getters
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({
users: [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
]
}),
getters: {
// 基本 getter
totalUsers: (state) => state.users.length,
// 带参数的 getter
getUserById: (state) => (id) => {
return state.users.find(user => user.id === id)
},
// 使用其他 store 的 getter
usersWithDetails(state) {
const counterStore = useCounterStore()
return state.users.map(user => ({
...user,
counter: counterStore.count
}))
}
}
})
// 在组件中使用
const userStore = useUserStore()
const totalUsers = userStore.totalUsers
const alice = userStore.getUserById(1) // { id: 1, name: 'Alice', age: 25 }
7. Actions
Actions 用于处理业务逻辑,可以是同步或异步的。
示例:Actions
// stores/todo.js
export const useTodoStore = defineStore('todo', {
state: () => ({
todos: [],
loading: false,
error: null
}),
actions: {
async fetchTodos() {
this.loading = true
this.error = null
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos')
if (!response.ok) {
throw new Error('Failed to fetch todos')
}
this.todos = await response.json()
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
},
addTodo(todo) {
this.todos.push({
id: Date.now(),
...todo,
completed: false
})
},
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
},
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id)
}
}
})
8. Pinia 与组合式 API
Pinia 可以与组合式 API 无缝集成,使用 defineStore() 的函数式写法。
示例:组合式 API 写法
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 状态
const count = ref(0)
const name = ref('Vue3')
// 计算属性
const doubleCount = computed(() => count.value * 2)
const doubleCountPlusOne = computed(() => doubleCount.value + 1)
// 方法
function increment() {
count.value++
}
async function fetchData() {
const response = await fetch('https://api.example.com/data')
const data = await response.json()
name.value = data.name
}
// 返回状态、计算属性和方法
return {
count,
name,
doubleCount,
doubleCountPlusOne,
increment,
fetchData
}
})
// 在组件中使用
const counter = useCounterStore()
console.log(counter.count) // 0
counter.increment() // count 变为 1
9. Pinia 插件
Pinia 支持插件系统,可以扩展 Pinia 的功能。
示例:创建 Pinia 插件
// pinia-plugin-logger.js
export function loggerPlugin({ store }) {
// 在 store 初始化时执行
console.log(`Store ${store.$id} 初始化`)
// 监听状态变化
store.$subscribe((mutation, state) => {
console.log(`Store ${store.$id} 状态变化:`, mutation, state)
})
// 监听 action 调用
store.$onAction(({ name, args, after, onError }) => {
console.log(`Store ${store.$id} 执行 action: ${name}`, args)
after((result) => {
console.log(`Store ${store.$id} action ${name} 成功:`, result)
})
onError((error) => {
console.error(`Store ${store.$id} action ${name} 失败:`, error)
})
})
}
// 在 main.js 中使用插件
import { createPinia } from 'pinia'
import { loggerPlugin } from './pinia-plugin-logger'
const pinia = createPinia()
pinia.use(loggerPlugin)
10. Pinia 最佳实践
- 将状态按功能模块划分到不同的 store 中
- 使用 TypeScript 定义 store 类型,获得更好的类型支持
- 对于复杂的业务逻辑,使用 actions 而不是直接修改状态
- 使用 getters 计算派生状态,避免重复计算
- 合理使用插件扩展 Pinia 功能
- 考虑使用 Pinia DevTools 进行调试
- 对于大型应用,可以考虑模块化 store