第74集:Getters计算属性进阶
概述
Pinia的Getters是Store的重要组成部分,用于处理派生状态。Getters类似于Vue组件中的计算属性,具有缓存机制,只有当依赖的状态变化时才会重新计算。掌握Getters的高级用法对于构建高效、可维护的状态管理系统至关重要。
核心知识点
1. Getters的基本定义
1.1 Options API风格
在Options API风格中,Getters通过getters选项定义:
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
users: [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Smith', age: 25 },
{ id: 3, name: 'Bob Johnson', age: 35 }
]
}),
getters: {
// 基本getter
doubleCount: (state) => state.count * 2,
// getter可以访问其他getters
doubleCountPlusOne: (state, getters) => getters.doubleCount + 1,
// getter可以返回函数,实现带参数的getter
getUserById: (state) => (id: number) => {
return state.users.find(user => user.id === id)
}
}
})1.2 Composition API风格
在Composition API风格中,Getters使用computed定义:
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export interface User {
id: number
name: string
age: number
}
export const useUserStore = defineStore('user', () => {
const users = ref<User[]>([
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Smith', age: 25 },
{ id: 3, name: 'Bob Johnson', age: 35 }
])
// 基本computed
const userCount = computed(() => users.value.length)
// computed可以访问其他computed
const hasUsers = computed(() => userCount.value > 0)
// 带参数的computed,使用闭包实现
const getUserById = computed(() => {
return (id: number) => users.value.find(user => user.id === id)
})
return {
users,
userCount,
hasUsers,
getUserById
}
})2. Getters的高级用法
2.1 带参数的Getters
Getters可以返回函数,实现带参数的查询功能:
// stores/product.ts
import { defineStore } from 'pinia'
export const useProductStore = defineStore('product', {
state: () => ({
products: [
{ id: 1, name: 'Laptop', category: 'electronics', price: 1000 },
{ id: 2, name: 'Phone', category: 'electronics', price: 500 },
{ id: 3, name: 'Chair', category: 'furniture', price: 100 }
]
}),
getters: {
// 带参数的getter:根据分类获取产品
getProductsByCategory: (state) => (category: string) => {
return state.products.filter(product => product.category === category)
},
// 带多个参数的getter:根据分类和价格范围获取产品
getProductsByCategoryAndPrice: (state) => (category: string, minPrice: number, maxPrice: number) => {
return state.products.filter(product =>
product.category === category &&
product.price >= minPrice &&
product.price <= maxPrice
)
}
}
})使用示例:
<template>
<div>
<h2>Electronics Products</h2>
<ul>
<li v-for="product in productStore.getProductsByCategory('electronics')" :key="product.id">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
<h2>Electronics Products under $600</h2>
<ul>
<li v-for="product in productStore.getProductsByCategoryAndPrice('electronics', 0, 600)" :key="product.id">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useProductStore } from '../stores/product'
const productStore = useProductStore()
</script>2.2 访问其他Store的Getters
Getters可以访问其他Store的状态和Getters:
// stores/cart.ts
import { defineStore } from 'pinia'
import { useProductStore } from './product'
export interface CartItem {
productId: number
quantity: number
}
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[]
}),
getters: {
// 访问其他Store的Getters
cartTotal: (state) => {
const productStore = useProductStore()
return state.items.reduce((total, item) => {
const product = productStore.products.find(p => p.id === item.productId)
return total + (product ? product.price * item.quantity : 0)
}, 0)
},
// 使用其他Store的带参数Getters
getItemPrice: (state) => {
const productStore = useProductStore()
return (productId: number) => {
const product = productStore.products.find(p => p.id === productId)
return product ? product.price : 0
}
}
}
})2.3 Getters的缓存机制
Getters具有缓存机制,只有当依赖的状态变化时才会重新计算:
// stores/counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
// 这个getter会被缓存
expensiveComputation: (state) => {
console.log('Computing expensive value...')
// 模拟昂贵的计算
for (let i = 0; i < 1000000; i++) {
// 一些计算
}
return state.count * 2
}
}
})使用示例:
<template>
<div>
<p>Count: {{ counterStore.count }}</p>
<p>Expensive: {{ counterStore.expensiveComputation }}</p>
<p>Expensive (cached): {{ counterStore.expensiveComputation }}</p>
<button @click="counterStore.count++">+</button>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>输出结果:
Computing expensive value...只有第一次访问和count变化时才会重新计算。
2.4 Getters中的this上下文
在Options API风格中,Getters可以使用this访问State和其他Getters:
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
users: [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Smith', age: 25 }
]
}),
getters: {
// 使用this访问state
userCount: function(state) {
return this.users.length // 等同于state.users.length
},
// 使用this访问其他getters
hasUsers: function() {
return this.userCount > 0
}
}
})3. Getters的类型系统
3.1 TypeScript类型推导
Pinia会自动推导Getters的返回类型:
// stores/counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
// 自动推导为number类型
doubleCount: (state) => state.count * 2,
// 自动推导为(x: number) => User | undefined类型
getUserById: (state) => (id: number) => {
return state.users.find(user => user.id === id)
}
}
})3.2 显式类型定义
可以为Getters添加显式类型定义:
// stores/user.ts
export interface User {
id: number
name: string
age: number
}
export const useUserStore = defineStore('user', {
state: () => ({
users: [] as User[]
}),
getters: {
// 显式类型定义
adultUsers: (state): User[] => {
return state.users.filter(user => user.age >= 18)
},
// 显式类型定义带参数的getter
getUserById: (state): ((id: number) => User | undefined) => {
return (id) => state.users.find(user => user.id === id)
}
}
})4. Getters的性能优化
4.1 避免在Getters中执行副作用
Getters应该是纯函数,不应该执行副作用:
// ❌ 错误:在getter中执行副作用
const badGetter = (state) => {
console.log('This is a side effect') // 副作用
return state.count * 2
}
// ✅ 正确:纯函数getter
const goodGetter = (state) => {
return state.count * 2
}4.2 合理使用带参数的Getters
带参数的Getters不会被缓存,每次调用都会重新计算:
// 这个getter每次调用都会重新计算
const getUserById = (state) => (id: number) => {
return state.users.find(user => user.id === id)
}优化建议:
- 对于频繁调用的带参数Getters,考虑缓存结果
- 对于大数据集,考虑使用索引或映射优化查找性能
4.3 使用计算属性缓存结果
对于复杂的计算,可以使用计算属性缓存结果:
// stores/product.ts
export const useProductStore = defineStore('product', () => {
const products = ref<Product[]>([])
const selectedCategory = ref<string>('all')
// 缓存过滤结果
const filteredProducts = computed(() => {
if (selectedCategory.value === 'all') {
return products.value
}
return products.value.filter(product => product.category === selectedCategory.value)
})
return {
products,
selectedCategory,
filteredProducts
}
})最佳实践
1. Getters设计原则
- 纯函数:Getters应该是纯函数,不执行副作用
- 单一职责:每个Getter只负责一个计算逻辑
- 缓存优先:优先使用缓存的Getter,避免频繁计算
- 类型安全:为所有Getter添加类型定义
2. Getters命名规范
- 使用描述性名称,如
userCount而不是count - 对于布尔类型的Getter,使用
is或has前缀,如isAuthenticated、hasUsers - 对于带参数的Getter,使用
get前缀,如getUserById
3. 复杂计算的拆分
对于复杂的计算,应该拆分为多个简单的Getter:
// ❌ 错误:复杂的单个getter
const complexGetter = (state) => {
const filtered = state.items.filter(item => item.active)
const sorted = filtered.sort((a, b) => a.price - b.price)
const total = sorted.reduce((sum, item) => sum + item.price, 0)
return { sorted, total }
}
// ✅ 正确:拆分多个简单getter
const activeItems = (state) => state.items.filter(item => item.active)
const sortedActiveItems = (state, getters) => [...getters.activeItems].sort((a, b) => a.price - b.price)
const totalActiveItemsPrice = (state, getters) => getters.sortedActiveItems.reduce((sum, item) => sum + item.price, 0)4. 带参数Getters的优化
对于频繁调用的带参数Getters,可以考虑缓存结果:
// stores/user.ts
export const useUserStore = defineStore('user', () => {
const users = ref<User[]>([])
const userCache = ref<Map<number, User>>(new Map())
// 监听users变化,更新缓存
watch(users, (newUsers) => {
const newCache = new Map()
newUsers.forEach(user => newCache.set(user.id, user))
userCache.value = newCache
}, { deep: true, immediate: true })
// 使用缓存的getter
const getUserById = computed(() => {
return (id: number) => {
return userCache.value.get(id)
}
})
return {
users,
getUserById
}
})常见问题与解决方案
1. Getters不更新
问题:修改了State,但Getters没有更新。
解决方案:
- 确保Getters依赖的State已经被修改
- 确保Getters是纯函数,没有依赖外部变量
- 检查是否使用了带参数的Getters,它们不会被缓存
2. 带参数的Getters性能问题
问题:频繁调用带参数的Getters导致性能问题。
解决方案:
- 使用缓存机制优化
- 考虑将带参数的Getters转换为普通的计算属性
- 使用索引或映射优化查找性能
3. TypeScript类型推导失败
问题:Getters的TypeScript类型推导失败。
解决方案:
- 确保tsconfig.json中启用了
strict: true - 为Getters添加显式类型定义
- 检查State的类型定义是否完整
4. Getters访问其他Store导致循环依赖
问题:Getters访问其他Store导致循环依赖错误。
解决方案:
- 重构Store,提取公共逻辑到第三个Store
- 在Getters内部延迟访问其他Store
- 使用事件总线解耦
进一步学习资源
课后练习
基础练习:
- 创建一个产品管理Store,包含产品列表
- 实现基本的Getters:产品数量、总价
- 实现带参数的Getters:根据分类获取产品
- 在组件中使用这些Getters
进阶练习:
- 创建一个用户管理Store,包含用户列表
- 实现复杂的Getters:成人用户、按年龄分组的用户
- 实现带多个参数的Getters:根据年龄范围和名称搜索用户
- 优化带参数Getters的性能
组合练习:
- 创建两个相互依赖的Store:产品Store和购物车Store
- 在购物车Store中访问产品Store的Getters
- 实现购物车总价、折扣计算等复杂Getters
- 测试Getters的缓存机制
性能优化练习:
- 创建一个包含大量数据的Store
- 实现复杂的计算逻辑
- 测试不同实现方式的性能差异
- 优化Getters的性能
通过本节课的学习,你应该能够掌握Pinia中Getters的高级用法,理解Getters的缓存机制,掌握Getters的类型系统,以及Getters的性能优化策略。这些知识将帮助你构建高效、可维护的状态管理系统,提高应用的性能和开发效率。