Pinia
State 修改与响应式细节
深入理解 Pinia 中 state 的修改方式,包括直接修改、$patch、$reset,以及响应式数组和对象的注意事项。
🎯 引言
学会这篇文章,你能掌握 Pinia 中修改 state 的三种方式,理解它们各自适合什么场景。同时你还会避开对象和数组操作中的响应式陷阱,让状态管理更稳。
✨ 直接修改:最简单
Pinia 允许直接修改 state,这是它和 Vuex 最大的不同之一。
stores/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
});
在组件中:
<template>
<p>{{ counter.count }}</p>
<button @click="counter.count++">加一</button>
<button @click="counter.count = 0">重置</button>
</template>
单个字段的修改用直接赋值最直观。但如果一次要改很多字段,或者修改逻辑比较复杂,就该考虑
$patch 或 action。🛠 $patch:批量修改
当一次需要修改多个 state 字段时,用 $patch 更清晰:
counter.$patch({
count: 10,
name: '李四',
});
$patch 也支持函数形式,适合需要基于当前状态计算的场景:
counter.$patch((state) => {
state.count++;
});
函数形式的
$patch 接收当前 state 作为参数,在里面可以直接改。这样写逻辑更集中,也更容易追踪。🔄 $reset:一键恢复初始值
如果你想把 state 恢复到 state() 函数里写的初始值,可以调用 $reset:
counter.$reset(); // count 变回 0
$reset 只对通过 state: () => ({}) 定义的初始状态有效。如果你之前用 $patch 整体替换了某个对象字段,恢复行为可能不符合预期。⚡ 响应式对象和数组的陷阱
替换整个对象
下面这种写法会破坏响应式:
const user = useUserStore();
user.info = { name: '李四', age: 25 }; // ❌ 可能丢失响应式
为什么?因为 info 的引用被整个换掉了,Pinia 内部对这个字段的响应式追踪可能会出问题。
正确做法:
// ✅ 逐个改属性
user.info.name = '李四';
user.info.age = 25;
// ✅ 或者用 $patch
user.$patch({
info: { name: '李四', age: 25 },
});
数组操作
数组的 push、splice 等方法都能保持响应式:
const list = useListStore();
list.items.push('新元素'); // ✅
list.items.splice(0, 1); // ✅
但下面这种直接通过索引替换整个对象要小心:
list.items[0] = { id: 2, name: '新' }; // ⚠️ 可能不触发更新
更安全的写法:
list.$patch((state) => {
state.items[0] = { id: 2, name: '新' };
});
Pinia 的 state 虽然响应式,但 JavaScript 对象和数组的某些赋值方式会让响应式追踪失效。遇到不确定的写法,优先用
$patch。💡 三种修改方式怎么选
| 方式 | 适用场景 | 例子 |
|---|---|---|
| 直接修改 | 单个字段简单赋值 | counter.count++ |
$patch | 批量修改、复杂对象/数组操作 | counter.$patch({...}) |
| action | 复杂业务逻辑、多处复用 | counter.add() |
🧾 小节总结
- Pinia 支持直接修改 state,适合简单场景。
$patch适合批量修改和复杂对象/数组操作。$reset可以把 state 恢复到初始值。- 替换整个对象引用可能导致响应式丢失,建议用
$patch或逐个修改属性。
❓ 知识问答
Q1:直接修改和 $patch 哪个更好?
没有绝对好坏。单个字段用直接修改更简洁,多个字段或复杂操作用 $patch 更清晰。
Q2:$reset 能重置 action 里修改过的状态吗?
能。$reset 只认 state() 函数返回的初始值,不管中间被什么方式修改过。
Q3:为什么替换整个对象会丢失响应式?
因为响应式系统追踪的是对象引用。你把引用换成一个新的普通对象,原来的响应式代理就断开了。
Q4:数组通过索引赋值一定失效吗?
不一定,但行为不如 push/splice/$patch 可靠。为了避免踩坑,建议用稳妥的写法。
🧪 小练习
补全下面的 usePointStore,实现:
points初始为0。add(n)action 给points加n。consume(n)action 消费积分,积分不足时不扣减。reset()action 把积分重置为0。
stores/point.js
import { defineStore } from 'pinia';
export const usePointStore = defineStore('point', {
state: () => ({
points: 0,
}),
actions: {
// 请在这里编写代码
},
});
🎉 恭喜你掌握了 state 修改的各种方式!下一篇我们学习 actions 中处理异步请求。
