第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,使用ishas前缀,如isAuthenticatedhasUsers
  • 对于带参数的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
  • 使用事件总线解耦

进一步学习资源

  1. Pinia官方文档 - Getters
  2. Vue 3官方文档 - 计算属性
  3. TypeScript官方文档 - 类型系统
  4. 函数式编程 - 纯函数
  5. 性能优化 - 缓存策略

课后练习

  1. 基础练习

    • 创建一个产品管理Store,包含产品列表
    • 实现基本的Getters:产品数量、总价
    • 实现带参数的Getters:根据分类获取产品
    • 在组件中使用这些Getters
  2. 进阶练习

    • 创建一个用户管理Store,包含用户列表
    • 实现复杂的Getters:成人用户、按年龄分组的用户
    • 实现带多个参数的Getters:根据年龄范围和名称搜索用户
    • 优化带参数Getters的性能
  3. 组合练习

    • 创建两个相互依赖的Store:产品Store和购物车Store
    • 在购物车Store中访问产品Store的Getters
    • 实现购物车总价、折扣计算等复杂Getters
    • 测试Getters的缓存机制
  4. 性能优化练习

    • 创建一个包含大量数据的Store
    • 实现复杂的计算逻辑
    • 测试不同实现方式的性能差异
    • 优化Getters的性能

通过本节课的学习,你应该能够掌握Pinia中Getters的高级用法,理解Getters的缓存机制,掌握Getters的类型系统,以及Getters的性能优化策略。这些知识将帮助你构建高效、可维护的状态管理系统,提高应用的性能和开发效率。

« 上一篇 State状态管理与响应式 - Pinia核心原理 下一篇 » Actions异步操作处理 - Pinia异步逻辑管理