第8章 状态管理
第23节 State与Getters
8.23.1 状态定义与响应式
状态定义
在Pinia中,状态是通过state选项定义的,它是一个返回初始状态对象的函数。这种函数形式确保了每个组件实例都能获得独立的状态副本。
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
// 基本类型
id: 1,
name: '张三',
email: 'zhangsan@example.com',
age: 30,
isActive: true,
// 数组类型
roles: ['user', 'editor'],
permissions: ['read', 'write'],
// 对象类型
profile: {
avatar: 'https://example.com/avatar.jpg',
bio: '热爱技术的开发者',
location: '北京'
},
// 嵌套对象
settings: {
notifications: {
email: true,
sms: false,
push: true
},
theme: 'light',
language: 'zh-CN'
}
})
})响应式状态自动解包
直接访问状态
在组件中使用Store时,Pinia会自动解包状态属性,我们可以直接访问它们,不需要使用.value(与Vue 3的ref不同)。
<template>
<div>
<h1>{{ userStore.name }}</h1> <!-- 直接访问,不需要.userStore.name.value -->
<p>{{ userStore.age }}</p> <!-- 直接访问 -->
</div>
</template>
<script setup>
import { useUserStore } from '../stores/user'
const userStore = useUserStore()
</script>解构状态
当我们需要解构状态时,需要使用storeToRefs函数来保持响应性,否则解构后的属性将失去响应性。
// 错误示例:解构后失去响应性
const { name, age } = userStore
name = '李四' // 不会更新Store中的状态
// 正确示例:使用storeToRefs保持响应性
import { storeToRefs } from 'pinia'
const { name, age } = storeToRefs(userStore)
name.value = '李四' // 会更新Store中的状态状态重置
使用$reset方法可以将Store的状态重置为初始值,这在需要重新初始化状态时非常有用。
// 重置所有状态
userStore.$reset()
// 示例:在组件中使用
const resetUser = () => {
userStore.$reset()
}状态变更
Pinia提供了多种方式来修改状态,我们可以根据不同的场景选择合适的方式:
直接修改:适合简单的状态更新
userStore.name = '李四' userStore.age++ userStore.isActive = false使用
$patch对象形式:适合同时修改多个状态userStore.$patch({ name: '李四', age: 31, isActive: false })使用
$patch函数形式:适合复杂的状态更新,如修改数组或嵌套对象userStore.$patch((state) => { // 修改数组 state.roles.push('admin') state.permissions = [...state.permissions, 'delete'] // 修改嵌套对象 state.settings.theme = 'dark' state.settings.notifications.sms = true // 复杂计算 state.age = new Date().getFullYear() - 1990 })使用Actions:适合包含业务逻辑或异步操作的状态更新
// 在Store中定义Action actions: { updateUserProfile(profileData) { // 可以添加业务逻辑 if (profileData.age < 18) { throw new Error('年龄必须大于18岁') } // 更新状态 this.$patch({ name: profileData.name, age: profileData.age, profile: { ...this.profile, ...profileData } }) } } // 在组件中使用 userStore.updateUserProfile({ name: '李四', age: 31, bio: '更新后的个人简介' })
8.23.2 Getters计算属性
Getters是Store中的计算属性,它们可以基于State派生出新的值。Getters会被缓存,只有当依赖的State发生变化时才会重新计算。
基本Getters
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({
id: 1,
name: '张三',
age: 30,
roles: ['user', 'editor'],
permissions: ['read', 'write']
}),
getters: {
// 简单Getters
isAdult: (state) => state.age >= 18,
isAdmin: (state) => state.roles.includes('admin'),
roleCount: (state) => state.roles.length,
// 使用模板字符串
userInfo: (state) => `${state.name} (${state.age}岁)`,
// 过滤数组
activePermissions: (state) => state.permissions.filter(perm => perm !== 'delete')
}
})访问其他Getters
在Getters中,我们可以使用this访问其他Getters,这允许我们组合多个Getters来创建更复杂的计算逻辑。
getters: {
isAdult: (state) => state.age >= 18,
// 使用this访问其他Getters
canEdit: function(state) {
return this.isAdult && state.roles.includes('editor')
},
// 箭头函数中不能使用this,需要直接访问state
canManageUsers: (state) => {
// 错误:箭头函数中this不指向Store实例
// return this.isAdmin && this.isAdult
// 正确:直接基于state计算
return state.roles.includes('admin') && state.age >= 18
}
}传递参数给Getters
Getters本身不能接受参数,但我们可以返回一个函数来实现类似效果。这种方式会导致Getters失去缓存能力,每次调用都会重新计算。
getters: {
// 返回函数,接受参数
getUserRole: (state) => (roleName) => {
return state.roles.find(role => role === roleName)
},
// 检查用户是否有特定权限
hasPermission: (state) => (permission) => {
return state.permissions.includes(permission)
},
// 根据条件过滤数据
filterPermissions: (state) => (allowed) => {
return state.permissions.filter(perm => allowed.includes(perm))
}
}在组件中使用带参数的Getters:
<template>
<div>
<p>是否为管理员:{{ userStore.isAdmin }}</p>
<p>是否有删除权限:{{ userStore.hasPermission('delete') }}</p>
<p>是否有编辑权限:{{ userStore.hasPermission('write') }}</p>
<h3>允许的权限:</h3>
<ul>
<li v-for="perm in userStore.filterPermissions(['read', 'write'])" :key="perm">
{{ perm }}
</li>
</ul>
</div>
</template>Getters的类型推导
Pinia会自动推导Getters的返回类型,在TypeScript项目中,我们可以获得完整的类型提示。
// TypeScript示例
export const useUserStore = defineStore('user', {
state: () => ({
age: 30,
roles: ['user', 'editor'] as const // 使用as const获得更精确的类型
}),
getters: {
// 自动推导返回类型为boolean
isAdult: (state) => state.age >= 18,
// 自动推导返回类型为'user' | 'editor' | undefined
getUserRole: (state) => (roleName: string) => {
return state.roles.find(role => role === roleName)
}
}
})8.23.3 StoreToRefs辅助函数
storeToRefs是Pinia提供的一个辅助函数,用于解构Store中的状态和Getters,并保持它们的响应性。
基本用法
import { storeToRefs } from 'pinia'
import { useUserStore } from '../stores/user'
const userStore = useUserStore()
// 解构状态并保持响应性
const { name, age, roles } = storeToRefs(userStore)
// 解构Getters并保持响应性
const { isAdmin, canEdit, userInfo } = storeToRefs(userStore)
// 使用解构后的状态和Getters
console.log(name.value) // 张三
console.log(isAdmin.value) // false
console.log(userInfo.value) // 张三 (30岁)注意事项
Actions不需要使用storeToRefs:Actions是函数,不需要响应式,所以可以直接解构。
const { updateUserProfile, resetUser } = userStore带参数的Getters不能使用storeToRefs:因为它们返回的是函数,不是响应式对象。
// 错误:带参数的Getters不能使用storeToRefs const { hasPermission } = storeToRefs(userStore) // 这会导致错误 // 正确:直接从Store实例访问 const { hasPermission } = userStorestoreToRefs只提取响应式属性:只有
state和getters会被转换为ref,actions和Store实例方法不会。
与computed的结合使用
我们可以将storeToRefs与Vue的computed结合使用,创建更复杂的计算属性。
<template>
<div>
<h1>{{ displayName }}</h1>
<p>{{ userStatus }}</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '../stores/user'
const userStore = useUserStore()
const { name, age, isAdmin } = storeToRefs(userStore)
// 结合computed创建更复杂的计算属性
const displayName = computed(() => {
return isAdmin.value ? `${name.value} (管理员)` : name.value
})
const userStatus = computed(() => {
if (age.value < 18) return '未成年'
if (isAdmin.value) return '管理员'
return '普通用户'
})
</script>最佳实践与注意事项
状态设计原则:
- 保持状态扁平化,避免过深的嵌套
- 每个Store负责一个特定的业务领域
- 状态应该是可序列化的(可以转换为JSON)
- 避免在状态中存储函数或非序列化数据
Getters使用建议:
- 用于派生状态,避免在组件中重复计算
- 简单的计算使用箭头函数
- 需要访问其他Getters的复杂计算使用普通函数
- 避免在Getters中执行异步操作
性能优化:
- 避免在Getters中执行昂贵的计算
- 带参数的Getters会失去缓存,谨慎使用
- 对于复杂计算,考虑使用
computed结合watch进行缓存
TypeScript支持:
- 使用
as const获得更精确的类型推导 - 为复杂状态定义接口
- 利用Pinia的自动类型推导,减少手动类型注解
- 使用
小结
本节我们深入学习了Pinia中的State与Getters,包括:
- 状态的定义和响应式特性
- 状态修改的多种方式(直接修改、$patch、Actions)
- Getters的基本使用和高级特性
- 如何传递参数给Getters
storeToRefs辅助函数的使用
合理使用State和Getters可以帮助我们构建清晰、可维护的状态管理系统。State用于存储原始数据,Getters用于派生计算数据,两者结合使用可以提高代码的可读性和复用性。
思考与练习
- 设计一个包含嵌套对象和数组的Store状态结构。
- 实现不同方式的状态修改,并比较它们的优缺点。
- 创建几个Getters,包括简单Getters、访问其他Getters的Getters和带参数的Getters。
- 使用
storeToRefs解构状态和Getters,并在组件中使用。 - 结合
computed创建更复杂的计算属性。 - 思考如何优化带参数的Getters的性能。