创建日期: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:数据本体
规则就三条,缺一不可:
- 必须是函数:
state: () => ({ ... })。函数保证每次(SSR 每个请求 / 测试每次)都返回全新对象,不会在多个 store 实例间共享同一个引用。 - state 里出现的键,就是
store.$state的键;store 实例会把 state 的每个属性代理出来,所以store.count即store.$state.count。 - 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,就写普通函数用
this:totalFormatted() { return '¥' + this.total; }。
在 store 内部 getter 里不能解构 state(会丢响应性)——要用就拿参数 s 或 this。
3. actions:改状态的操作(同步/异步都行)
规则:
- 就是普通函数,没有 mutation——想怎么改就怎么改;
this指向整个 store,能读 state、getters、调别的 action;- 支持 async:
await完再改 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 的 action 用this拿不到——跨 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
读三个数字:
- getter 会跟着 state 自动重算:coupon 从 0→20,total 立刻 188→168;
$patch对象是浅合并、函数是任意改:函数把items[1].qty改成 2 且 coupon 改 5,total 一步到位 312;$reset()把 state 打回state()出厂值:items 清空、coupon 归 0——但注意它只管 state,不还原 getters/actions(那些本来也不是状态)。
5. 常见坑速查
| # | 坑 | 解法 |
|---|---|---|
| 1 | state: { count: 0 }(对象非函数) | 必须是函数,否则多实例/SSR 共享引用 |
| 2 | 组件里 const { count } = store 不响应 | storeToRefs(store)(篇 3) |
| 3 | getter 返回“每次新对象”(如 (s) => s.items.filter(...)) | 会在依赖没变时仍被当新值 → 尽量返回原始引用;要稳定引用用 computed 存结果 |
| 4 | async action 忘 await 拿返回值 | await store.submit() |
| 5 | 想在 action 里调“别的 store 的 state” | 不能 this,先 const o = useOtherStore() |
动手
- 给 cart 加一个
discount状态和一个useCoupon(code)action(code 对才改 coupon); - 把 getter
total改成过滤商品后再求和,观察 getter 的缓存时机; - 试
$patch对象传一个嵌套的{ coupon: {…} }——对象形式是浅合并,嵌套会整体替换,体会为什么复杂改动用函数形式。
自测
state为什么必须是函数?- 改 state 的两种合法姿势?
$patch的两种入参各是什么语义? - getter 什么时候重算?怎么让一个 getter 用另一个 getter?
- action 里怎么发起异步再改 state?返回值怎么拿?
$reset()能还原 getters/actions 吗?