Skip to content

Store 的职责与响应式消费 ​

Pinia 解决的是跨组件共享的领域状态,不是把所有变量搬进全局。先问“谁拥有这份状态”:输入框只被一个组件使用,就留在组件;购物车、当前用户和筛选条件被多个页面读写,才值得成为 store;服务器返回的列表还需要缓存、失效和重取策略时,应优先交给 server-state 工具。

从一个可观察的状态开始 ​

下面的例子只依赖 Pinia 的公开 API,先看数据流再看语法:

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

export const useCartStore = defineStore('cart', () => {
  const items = ref<{ id: string; price: number; quantity: number }[]>([])
  const count = computed(() => items.value.reduce((n, item) => n + item.quantity, 0))
  const total = computed(() => items.value.reduce((n, item) => n + item.price * item.quantity, 0))
  function add(item: { id: string; price: number }) {
    const existing = items.value.find((entry) => entry.id === item.id)
    if (existing) existing.quantity += 1
    else items.value.push({ ...item, quantity: 1 })
  }
  return { items, count, total, add }
})

const cart = useCartStore()
const { count, total } = storeToRefs(cart)

items 是唯一事实,count 和 total 是派生事实,add 是改变事实的动作。组件解构 store 时必须对状态和 getter 使用 storeToRefs,否则直接解构会把响应式引用变成一次性快照;action 可以直接解构,因为函数身份不依赖响应式追踪。

两种定义风格如何取舍 ​

Setup Store 用 ref、computed 和函数表达领域模型,适合复用组合式函数与精确类型;Option Store 用 state、getters、actions 分栏,迁移 Vuex 或让初学者快速定位职责时更直观。两者都创建同一种 store,选择标准是团队约定、迁移成本和类型复杂度,而不是“哪一种更现代”。

边界 ​

不要在 getter 里写请求、修改 state 或读取当前时间;getter 应当是可重复的派生计算。不要把 isLoading、error 和 items.length === 0 混成一个状态:空结果、加载中和失败是不同事实。跨 store 读取时保持单向依赖,避免 user 读 cart、cart 又读 user 形成初始化环。

练习 ​

把购物车改成支持“删除一项”和“按商品 id 查询数量”,要求:删除不存在的 id 不改变数组引用;商品数量为 0 时移除;组件仍能观察到 count 与 total 的变化。先写两个输入序列,再说明每一步可观察结果。

下一步 ​

完成练习后打开本章答案,再回到Pinia 课程总览。

用心记录每一步成长