跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 本机 pinia 4.0.3 + vue 3.5.42 实测;带 [实测] 的输出均真实。上一篇:Pinia 入门

Pinia 三件套:state / getters / actions(options store)

一句话:一个 options store 就是三个字段——state() 产数据、getters 算派生、actions 提供改数据的操作。理解它们各自的「响应性规则」,Pinia 就学完一半。本篇全用可跑代码实测。

1. state:数据本体

规则就三条,缺一不可:

  1. 必须是函数state: () => ({ ... })。函数保证每次(SSR 每个请求 / 测试每次)都返回全新对象,不会在多个 store 实例间共享同一个引用。
  2. state 里出现的键,就是 store.$state 的键;store 实例会把 state 的每个属性代理出来,所以 store.countstore.$state.count
  3. state 里的所有属性默认都是响应式的——但要改的是“属性”,不是“store 这个对象”。
import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', {
state: () => ({
items: [], // 数组里塞对象也可以,只要是从 state() 里来的
coupon: 0,
}),
// ...getters / actions 见下
});

怎么改:直接赋值 或 $patch

直接赋值(主角):

store.coupon = 10;
store.items.push({ name: '鼠标', price: 129, qty: 1 }); // 数组用原生方法即可

$patch(配角):一次性改多个、或在 devtools 里把这次改动当“一条记录”看。

  • 对象:浅合并,store.$patch({ coupon: 10 })
  • 函数:给你 state 本体随便改,store.$patch(s => { s.items[1].qty = 2; s.coupon = 5; })

$patch 的本质:对象形式是浅合并,函数形式是 (state) => void 直接改。devtools 会把一次 $patch 显示成一条 mutation,而三次直接赋值是三条——这就是唯一实际区别。

2. getters:派生值(带缓存的计算属性)

规则

  • 写法像 methods,但结果被包成 computed只有当它依赖的 state 变了才重算,否则命中缓存
  • 推荐用箭头函数收 state 参数:double: (s) => s.n * 2
  • 想用别的 getter / action,就写普通函数thistotalFormatted() { return '¥' + this.total; }

在 store 内部 getter 里不能解构 state(会丢响应性)——要用就拿参数 sthis

3. actions:改状态的操作(同步/异步都行)

规则

  • 就是普通函数,没有 mutation——想怎么改就怎么改;
  • this 指向整个 store,能读 state、getters、调别的 action;
  • 支持 asyncawait 完再改 state 即可(前面入门篇提醒过:async action 要 await 才拿得到结果,别只管调不管等)。
actions: {
add(name, price, qty = 1) { this.items.push({ name, price, qty }); },
async submit() {
await new Promise(r => setTimeout(r, 10)); // 模拟请求
this.coupon = this.total >= 100 ? 10 : 0; // 请求回来再改 state
return this.total; // 也允许有返回值
},
}

action 里不要直接调 useXxxStore() 里的另一个 store 的 actionthis 拿不到——跨 store 要 import { useOtherStore }const other = useOtherStore(); other.foo()篇 2 细讲)。

4. 一整套实测(cart:加购 → 折扣 → 提交 → 重置)

import { createPinia, setActivePinia, defineStore } from 'pinia';
setActivePinia(createPinia());

const useCart = defineStore('cart', {
state: () => ({ items: [], coupon: 0 }),
getters: {
count: (s) => s.items.length,
total: (s) => s.items.reduce((a, i) => a + i.price * i.qty, 0) - s.coupon,
totalFormatted() { return `¥${this.total}`; }, // 用 this 调别的 getter
},
actions: {
add(name, price, qty = 1) { this.items.push({ name, price, qty }); },
async submit() {
await new Promise((r) => setTimeout(r, 10));
this.coupon = this.total >= 100 ? 10 : 0; // 满 100 减 10
return this.total;
},
},
});

const c = useCart();
c.add('js 书', 59); c.add('鼠标', 129, 1);
console.log('count=', c.count, 'total=', c.total, 'totalFormatted=', c.totalFormatted);
console.log('$patch 对象批量:');
c.$patch({ coupon: 20 });
console.log(' coupon=', c.coupon, 'total=', c.total);
console.log('$patch 函数批量:');
c.$patch((s) => { s.items[1].qty = 2; s.coupon = 5; });
console.log(' items[1].qty=', c.items[1].qty, 'total=', c.total);
const paid = await c.submit();
console.log('async submit 返回=', paid, 'coupon 自动折扣=', c.coupon);
c.$reset();
console.log('$reset 后:items 空=', c.items.length === 0, 'coupon=', c.coupon);
[实测] 上面脚本的输出(真实运行):
count= 2 total= 188 totalFormatted= ¥188
$patch 对象批量:
coupon= 20 total= 168
$patch 函数批量:
items[1].qty= 2 total= 312
async submit 返回= 307 coupon 自动折扣= 10
$reset 后:items 空= true coupon= 0

读三个数字:

  1. getter 会跟着 state 自动重算:coupon 从 0→20,total 立刻 188→168;
  2. $patch 对象是浅合并、函数是任意改:函数把 items[1].qty 改成 2 且 coupon 改 5,total 一步到位 312;
  3. $reset() 把 state 打回 state() 出厂值:items 清空、coupon 归 0——但注意它只管 state,不还原 getters/actions(那些本来也不是状态)。

5. 常见坑速查

#解法
1state: { count: 0 }(对象非函数)必须是函数,否则多实例/SSR 共享引用
2组件里 const { count } = store 不响应storeToRefs(store)篇 3
3getter 返回“每次新对象”(如 (s) => s.items.filter(...)会在依赖没变时仍被当新值 → 尽量返回原始引用;要稳定引用用 computed 存结果
4async action 忘 await 拿返回值await store.submit()
5想在 action 里调“别的 store 的 state”不能 this,先 const o = useOtherStore()

动手

  1. 给 cart 加一个 discount 状态和一个 useCoupon(code) action(code 对才改 coupon);
  2. 把 getter total 改成过滤商品后再求和,观察 getter 的缓存时机;
  3. $patch 对象传一个嵌套{ coupon: {…} }——对象形式是浅合并,嵌套会整体替换,体会为什么复杂改动用函数形式。

自测

  1. state 为什么必须是函数?
  2. 改 state 的两种合法姿势?$patch 的两种入参各是什么语义?
  3. getter 什么时候重算?怎么让一个 getter 用另一个 getter?
  4. action 里怎么发起异步再改 state?返回值怎么拿?
  5. $reset() 能还原 getters/actions 吗?

下一篇:组合式 store 与多 store 协作