Getters 计算属性 #

什么是 Getters? #

Getters 类似于 Vue 的 computed 属性,用于定义派生状态。它们会自动缓存计算结果,只有依赖的状态变化时才会重新计算。

定义 Getters #

基本语法 #

ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    // 箭头函数
    doubleCount: (state) => state.count * 2,
    
    // 带类型的箭头函数
    tripleCount: (state): number => state.count * 3
  }
})

访问 State #

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    firstName: 'John',
    lastName: 'Doe'
  }),
  getters: {
    // 使用 state 参数
    fullName: (state) => `${state.firstName} ${state.lastName}`
  }
})

访问其他 Getter #

使用 this 访问其他 getter:

ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    
    // 使用 this 访问其他 getter
    quadrupleCount() {
      return this.doubleCount * 2
    }
  }
})

使用 Getters #

在组件中访问 #

vue
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.doubleCount }}</p>
    <p>Quadruple: {{ counter.quadrupleCount }}</p>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

解构使用 #

vue
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// 使用 storeToRefs 解构
const { doubleCount, quadrupleCount } = storeToRefs(counter)
</script>

传递参数 #

Getters 本身不接受参数,但可以返回一个函数:

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [
      { id: 1, name: 'John', age: 25 },
      { id: 2, name: 'Jane', age: 30 },
      { id: 3, name: 'Bob', age: 20 }
    ]
  }),
  getters: {
    // 返回一个函数
    getUserById: (state) => {
      return (id: number) => state.users.find(user => user.id === id)
    },
    
    // 带多个参数
    getUsersByAgeRange: (state) => {
      return (min: number, max: number) => 
        state.users.filter(user => user.age >= min && user.age <= max)
    }
  }
})
vue
<template>
  <p>{{ userStore.getUserById(1)?.name }}</p>
  <p>{{ userStore.getUsersByAgeRange(20, 30).length }} users</p>
</template>

注意事项 #

返回函数的 getter 不会被缓存:

ts
// 每次调用都会重新执行
const user = userStore.getUserById(1)  // 执行一次
const user2 = userStore.getUserById(1) // 再次执行

如果需要缓存,可以在组件中使用 computed:

vue
<script setup>
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 缓存结果
const user = computed(() => userStore.getUserById(1))
</script>

访问其他 Store #

ts
export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [
      { id: 1, productId: 101, quantity: 2 },
      { id: 2, productId: 102, quantity: 1 }
    ]
  }),
  getters: {
    summary(): string {
      const userStore = useUserStore()
      return `${userStore.name} 的购物车有 ${this.items.length} 件商品`
    }
  }
})

TypeScript 类型 #

自动类型推断 #

ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    // 自动推断为 number
    doubleCount: (state) => state.count * 2
  }
})

显式类型注解 #

ts
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [] as User[]
  }),
  getters: {
    // 显式指定返回类型
    userCount: (state): number => state.users.length,
    
    // 返回可能为 undefined
    firstUser: (state): User | undefined => state.users[0],
    
    // 返回函数类型
    getUserById: (state): (id: number) => User | undefined => {
      return (id) => state.users.find(user => user.id === id)
    }
  }
})

Setup Store 中的 Getters #

在 Setup Store 中使用 computed

ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  // 使用 computed 定义 getter
  const doubleCount = computed(() => count.value * 2)
  const quadrupleCount = computed(() => doubleCount.value * 2)
  
  return {
    count,
    doubleCount,
    quadrupleCount
  }
})

实际示例 #

购物车 Store #

ts
// stores/cart.ts
import { defineStore } from 'pinia'

interface CartItem {
  id: number
  productId: number
  name: string
  price: number
  quantity: number
}

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[],
    discount: 0
  }),
  
  getters: {
    // 商品总数
    itemCount: (state) => 
      state.items.reduce((sum, item) => sum + item.quantity, 0),
    
    // 商品种类数
    uniqueItemCount: (state) => state.items.length,
    
    // 小计
    subtotal: (state) => 
      state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    
    // 折扣金额
    discountAmount(): number {
      return this.subtotal * (this.discount / 100)
    },
    
    // 总计
    total(): number {
      return this.subtotal - this.discountAmount
    },
    
    // 是否为空
    isEmpty: (state) => state.items.length === 0,
    
    // 获取商品
    getItemById: (state) => {
      return (productId: number) => 
        state.items.find(item => item.productId === productId)
    },
    
    // 格式化总价
    formattedTotal(): string {
      return `¥${this.total.toFixed(2)}`
    }
  },
  
  actions: {
    // ...
  }
})

Todo Store #

ts
// stores/todo.ts
import { defineStore } from 'pinia'

interface Todo {
  id: number
  text: string
  completed: boolean
  priority: 'low' | 'medium' | 'high'
}

export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: [] as Todo[],
    filter: 'all' as 'all' | 'active' | 'completed'
  }),
  
  getters: {
    // 过滤后的 todos
    filteredTodos: (state) => {
      switch (state.filter) {
        case 'active':
          return state.todos.filter(todo => !todo.completed)
        case 'completed':
          return state.todos.filter(todo => todo.completed)
        default:
          return state.todos
      }
    },
    
    // 统计数据
    totalCount: (state) => state.todos.length,
    activeCount: (state) => state.todos.filter(todo => !todo.completed).length,
    completedCount: (state) => state.todos.filter(todo => todo.completed).length,
    
    // 完成百分比
    completionRate(): number {
      if (this.totalCount === 0) return 0
      return Math.round((this.completedCount / this.totalCount) * 100)
    },
    
    // 按优先级分组
    todosByPriority: (state) => {
      return {
        high: state.todos.filter(todo => todo.priority === 'high'),
        medium: state.todos.filter(todo => todo.priority === 'medium'),
        low: state.todos.filter(todo => todo.priority === 'low')
      }
    },
    
    // 是否全部完成
    allCompleted: (state) => 
      state.todos.length > 0 && state.todos.every(todo => todo.completed),
    
    // 获取 todo
    getTodoById: (state) => {
      return (id: number) => state.todos.find(todo => todo.id === id)
    }
  }
})

Getters 最佳实践 #

1. 保持简单 #

ts
// 推荐:简单的计算
getters: {
  doubleCount: (state) => state.count * 2
}

// 不推荐:复杂的逻辑(应该放在 action 中)
getters: {
  complexCalculation: (state) => {
    // 复杂的异步操作或副作用
  }
}

2. 利用缓存 #

ts
// 推荐:getter 会自动缓存
getters: {
  expensiveCalculation: (state) => {
    // 复杂计算,但会被缓存
    return state.items.reduce(/* ... */)
  }
}

3. 避免副作用 #

ts
// 不推荐:在 getter 中产生副作用
getters: {
  badGetter: (state) => {
    console.log('side effect')  // 副作用
    fetch('/api/data')          // 异步请求
    return state.value
  }
}

// 推荐:getter 只做纯计算
getters: {
  goodGetter: (state) => {
    return state.value * 2
  }
}

4. 合理使用参数 #

ts
// 对于需要参数的场景
getters: {
  // 返回函数
  getItemById: (state) => (id: number) => 
    state.items.find(item => item.id === id)
}

// 注意:返回函数的 getter 不会被缓存

下一步 #

现在你已经掌握了 Getters 的使用,接下来让我们学习 Actions。

最后更新:2026-03-28