pinia 的 watch 必须在组件或组合式函数中调用,监听 store 的响应式属性或计算值;支持单字段、多状态组合及跨 store 派生状态监听,需注意浅/深监听、避免循环更新和自动清理机制。

Pinia 的 watch 不是直接写在 store 里,而是要在组件(或组合式函数)中调用,监听 store 的响应式属性或计算值。关键在于:要监听什么、怎么取值、是否需要深度监听、以及如何避免不必要的触发。
监听单个状态字段(基础用法)
最常见的是监听某个具体字段,比如用户登录态、主题色、语言等。需注意:必须通过 store.xxx 访问,不能直接传字符串名。
- 监听基本类型(如
count)默认浅监听,值变即触发 - 监听对象或数组时,若想捕获内部属性变化,要加
{ deep: true } - 回调函数接收新值、旧值(首次执行时旧值为
undefined)
const userStore = useUserStore()
watch(() => userStore.token, (newToken, oldToken) => {
if (newToken && !oldToken) console.log('用户已登录')
})
监听多个状态或计算属性(组合监听)
跨模块联动常需同时关注几个状态,比如“订单模块”要响应“购物车数量 + 用户地址 + 支付方式”的变化。这时可把它们包装成一个数组或计算函数返回的对象。
- 传入数组:各元素分别取值,watch 回调接收对应位置的新旧值数组
- 传入 getter 函数:更灵活,可做逻辑合并(如判断是否满足下单条件)
- 推荐用函数方式,语义清晰且便于复用
const cartStore = useCartStore()
const userStore = useUserStore()
const orderStore = useOrderStore()
watch(
() => ({
cartCount: cartStore.count,
addressId: userStore.addressId,
payMethod: orderStore.payMethod
}),
({ cartCount, addressId, payMethod }) => {
if (cartCount > 0 && addressId && payMethod) {
orderStore.enableSubmit = true
}
},
{ immediate: true } // 首次创建时也执行一次
)
监听其他 store 的派生状态(跨模块核心技巧)
Pinia 没有内置“跨 store 依赖”,但你可以主动 import 并在当前作用域使用。只要被监听的值是响应式的(即来自 store.xxx 或 store.$state.xxx),watch 就能追踪。
- 避免在 store 内部调用
watch(违反组合式逻辑分离原则) - 在页面或业务逻辑层统一监听,更易维护和测试
- 如果多个组件都需要同样联动逻辑,建议封装成自定义 Hook(如
useOrderReadyWatcher())
store.$state 整体(除非真需要全量响应),它会因任意字段变更而频繁触发;优先监听明确字段或精简计算值。
注意事项与避坑点
-
组件卸载时自动清理:在
setup()或onMounted中使用的watch,Vue 会自动在组件 unmount 时停止监听 —— 无需手动stop() -
避免在 watch 回调中修改被监听源(如监听
a却又在回调里改a),可能造成循环更新 -
异步操作请谨慎:比如监听 token 后发请求,记得处理重复触发(加防抖、或用
watchEffect+ 标记位) -
不支持监听 actions:watch 只响应响应式数据变化,调用 action 不会触发,如需监听行为,请在 action 内部手动
emit或更新关联状态










