第8章 状态管理
第22节 Pinia基础
8.22.1 Pinia安装与配置
什么是Pinia?
Pinia是Vue官方推荐的状态管理库,用于替代Vuex 4。它提供了更简洁的API、更好的TypeScript支持和更灵活的结构,同时保留了Vuex的核心概念。
安装Pinia
使用npm安装:
npm install pinia使用yarn安装:
yarn add pinia使用pnpm安装:
pnpm add pinia配置Pinia
在Vue应用中配置Pinia非常简单,只需要在main.js或main.ts中创建并使用Pinia实例:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia' // 导入createPinia函数
// 创建Pinia实例
const pinia = createPinia()
// 创建Vue应用实例
const app = createApp(App)
// 使用Pinia
app.use(pinia)
// 挂载应用
app.mount('#app')配置文件结构
推荐的文件结构如下:
src/
├── stores/ # 存放所有Store
│ ├── counter.js # 计数器Store示例
│ ├── user.js # 用户Store示例
│ └── index.js # Store出口文件(可选)
├── main.js # 应用入口
└── App.vue # 根组件8.22.2 Store定义与使用
创建第一个Store
使用defineStore函数创建Store,它接收两个参数:
- Store的唯一名称(字符串)
- Store配置对象,包含
state、getters和actions
// stores/counter.js
import { defineStore } from 'pinia'
// 定义并导出Store
export const useCounterStore = defineStore('counter', {
// 状态:返回初始状态对象的函数
state: () => ({
count: 0,
name: 'Eduardo',
isAdmin: false,
items: []
}),
// Getters:计算属性
getters: {
// 简单Getters
doubleCount: (state) => state.count * 2,
// 使用this访问其他Getters
doubleCountPlusOne() {
return this.doubleCount + 1
},
// 带参数的Getters
getItemById: (state) => (id) => {
return state.items.find(item => item.id === id)
}
},
// Actions:修改状态的方法(可以是异步的)
actions: {
// 同步Action
increment() {
this.count++
},
// 带参数的Action
incrementBy(value) {
this.count += value
},
// 异步Action
async fetchItems() {
// 模拟API请求
const response = await new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
])
}, 1000)
})
this.items = response
},
// 使用其他Actions
async refreshItems() {
await this.fetchItems()
// 可以添加其他逻辑
}
}
})在组件中使用Store
在组件中使用Store非常简单,只需要导入并调用我们定义的Store函数即可:
选项式API中使用
<template>
<div>
<h1>{{ counterStore.name }}</h1>
<p>Count: {{ counterStore.count }}</p>
<p>Double Count: {{ counterStore.doubleCount }}</p>
<p>Double Count Plus One: {{ counterStore.doubleCountPlusOne }}</p>
<button @click="counterStore.increment">Increment</button>
<button @click="counterStore.incrementBy(5)">Increment by 5</button>
<button @click="counterStore.fetchItems">Fetch Items</button>
<ul>
<li v-for="item in counterStore.items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
import { useCounterStore } from '../stores/counter'
export default {
data() {
return {
counterStore: useCounterStore()
}
}
}
</script>组合式API中使用
<template>
<div>
<h1>{{ counter.name }}</h1>
<p>Count: {{ counter.count }}</p>
<p>Double Count: {{ counter.doubleCount }}</p>
<p>Double Count Plus One: {{ counter.doubleCountPlusOne }}</p>
<button @click="counter.increment">Increment</button>
<button @click="counter.incrementBy(5)">Increment by 5</button>
<button @click="fetchItems">Fetch Items</button>
<ul>
<li v-for="item in counter.items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/counter'
// 获取Store实例
const counter = useCounterStore()
// 调用异步Action
const fetchItems = async () => {
await counter.fetchItems()
}
</script>使用storeToRefs保持响应性
当我们需要解构Store中的状态时,可以使用storeToRefs函数来保持响应性:
<template>
<div>
<h1>{{ name }}</h1>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia' // 导入storeToRefs
// 获取Store实例
const counter = useCounterStore()
// 解构状态并保持响应性
const { name, count, doubleCount } = storeToRefs(counter)
// Actions不需要使用storeToRefs
const { increment } = counter
</script>修改状态的几种方式
直接修改状态:
counter.count++ counter.name = 'New Name'使用
$patch方法(修改多个状态):counter.$patch({ count: counter.count + 1, name: 'Updated Name', isAdmin: true })使用
$patch方法(函数形式,适合复杂修改):counter.$patch((state) => { state.count++ state.items.push({ id: 4, name: 'Item 4' }) })使用Actions(推荐用于复杂逻辑):
counter.increment() counter.incrementBy(5) await counter.fetchItems()
重置状态
使用$reset方法可以将Store状态重置为初始值:
counter.$reset()监听状态变化
使用$subscribe方法可以监听状态变化:
// 监听所有状态变化
counter.$subscribe((mutation, state) => {
// mutation.type: 'direct' | 'patch object' | 'patch function'
// mutation.storeId: 'counter'
// mutation.payload: 传递给$patch的参数
console.log('State changed:', mutation, state)
})
// 监听特定状态变化
counter.$subscribe((mutation, state) => {
if (mutation.payload && 'count' in mutation.payload) {
console.log('Count changed:', state.count)
}
})最佳实践与注意事项
Store命名规范:
- Store名称使用驼峰命名法,如
useCounterStore - 文件名与Store名称对应,如
counter.js对应useCounterStore
- Store名称使用驼峰命名法,如
状态设计原则:
- 状态应该是扁平化的,避免深层嵌套
- 每个Store负责一个特定的业务领域
- 状态应该是可序列化的(可以转换为JSON)
Actions vs 直接修改状态:
- 简单的状态修改可以直接进行
- 复杂的逻辑、异步操作或需要记录的操作应该放在Actions中
- Actions可以被其他Actions调用,便于代码复用
Getters的使用:
- 计算派生状态使用Getters
- Getters可以缓存结果,提高性能
- 复杂的计算逻辑应该放在Getters中
TypeScript支持:
- Pinia提供了良好的TypeScript支持
- 可以为状态、Getters和Actions添加类型注解
- 在VS Code中可以获得完整的类型提示
小结
本节我们学习了Pinia的基础知识,包括:
- Pinia的安装与配置
- Store的定义(state、getters、actions)
- 在组件中使用Store(选项式API和组合式API)
- 使用
storeToRefs保持响应性 - 修改状态的多种方式
- 状态重置和监听
Pinia是一个强大而灵活的状态管理库,它的API简洁易用,同时提供了良好的TypeScript支持。通过合理使用Pinia,我们可以更好地管理应用的状态,提高代码的可维护性和可扩展性。
思考与练习
- 安装Pinia并配置到Vue应用中。
- 创建一个计数器Store,包含count状态、doubleCount getter和increment action。
- 在组件中使用该Store,实现计数器功能。
- 添加一个异步Action,模拟从API获取数据。
- 尝试使用
$patch方法修改多个状态。 - 使用
storeToRefs解构Store状态并保持响应性。 - 实现状态重置功能。