用Vue3 watch监听Pinia状态?哪些坑容易踩?怎么监听才高效省心?
最近在技术交流群里刷到好多刚上手Pinia的Vue3开发者提问:“我直接把watch套Pinia store实例上,怎么有时候不更新有时候重复触发啊?”“要监听store里嵌套好深的对象,应该怎么写才对?”“computed和watch到底什么时候选哪个配Pinia?”这些问题其实我刚从Vuex转Pinia的时候也踩过不少坑,当时翻了好多社区帖子和官方文档才搞明白,今天就把这些经验整理成大家能直接用的干货,不用再东找西找踩弯路了。
搞清楚3个核心前提:别一开始就用错了API
监听Pinia状态之前,得先把几个基础概念和API搞懂,不然写出来的代码要么有性能问题,要么直接没效果,这三个前提可以说是“地基”,地基稳了后面的房子才能盖得牢。
第一个前提:Pinia的store实例本质上是响应式对象,但要区分整体和部分
很多人一开始会把整个store传进watch的第一个参数,
import { watch } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// ❌ 这是个很容易犯的错误!
watch(userStore, () => {
console.log('store里有东西变了!')
})
乍一看没问题,Pinia的store确实是reactive对象啊,但是很多时候你会发现,这个watch要么没反应,要么一更新就触发好多次,没反应的原因可能是Pinia的store默认是“浅层响应式暴露内部属性”?不对不对,其实Pinia在初始化store的时候,会把state、getters都用reactive或者computed处理好挂在实例上,整体store是reactive的没错,但你这样直接传整个store,相当于监听了整个对象的所有引用,包括那些不会变的actions——哦对,actions是函数,引用不会变,所以如果只有state或者getters变,其实watch是能捕获到浅层变化的?那为什么会有“没反应”的情况?等下,哦,如果你监听的是深层嵌套的state,但又没开deep,那直接传store实例确实会有问题,因为watch默认是浅层监听的。
不过直接传整个store实例,哪怕加了deep,性能也会特别差——只要store里任何一个角落的state动了,不管是你需要的还是不需要的,这个watch都会触发,所以除非你真的要监听store里的所有东西,否则千万别这么干。
那要监听store里的部分内容,应该怎么写?这里官方其实给了推荐的方式:用getter函数的形式,返回你要监听的那个具体的state或者getter,或者用解构赋值把state和getters取出来用ref再包一层?不对,解构赋值的话,如果你解的是state的属性,那如果这些属性是基本类型的话,解构出来的就是普通值,不是响应式的了;如果是引用类型的话,解构出来的引用还是指向store里的那个对象,倒是能监听,但不够规范,而且容易出错,所以最规范、性能最好的方式,还是用getter函数的形式写watch的第一个参数。
第二个前提:区分watch的三个变体,结合Pinia场景选对
Vue3里的watch不是只有一种,它有三个常用的变体:普通watch、watchEffect、watchPostEffect,这三个在监听Pinia状态的时候,表现是完全不一样的,得结合你的需求场景来选。
先说说普通watch,这也是大家用得最多的,它的特点是懒执行——也就是组件刚挂载的时候不会自动跑,只有当你监听的源发生变化的时候才会触发;而且它可以明确指定监听的源,还能拿到变化前的值和变化后的值,普通watch特别适合那种“只有某个特定状态变了才需要做操作,而且需要对比前后值”的场景,比如监听用户登录状态的变化,登录成功了就跳转到首页,退出登录就清空购物车;或者监听搜索关键词的变化,关键词变了就调用搜索接口——这里还要注意防抖哦,不过防抖是额外的优化,和Pinia没关系。
然后是watchEffect,它和普通watch刚好相反,是立即执行的——组件刚挂载的时候就会自动跑一次;而且它是自动追踪依赖的,不需要你明确指定监听的源,只要你在回调函数里用到了任何响应式数据(包括Pinia的state、getters),当这些数据变化的时候,它就会自动触发;但它拿不到变化前的值,watchEffect适合那种“只要用到的状态变了就更新,不需要对比前后值”的场景,比如监听Pinia里的主题色状态,主题色变了就自动修改document.body的样式;或者监听用户的token,token变了就自动把它存到localStorage里——不过这里要注意,存localStorage最好是用watch,而不是watchEffect?为什么?因为watchEffect会立即执行一次,所以如果你的token本来就在localStorage里,刚从Pinia取出来的时候,watchEffect就会把它又存一遍,虽然存相同的值没问题,但有点多余;而且如果后面要修改token的存储逻辑,比如加密,每次重复加密也是浪费性能,所以存localStorage这种“需要状态变化才执行,不需要立即执行”的场景,还是用普通watch加个immediate: true?不对,immediate: true的话也会立即执行一次,那和watchEffect存localStorage的问题一样啊?哦,这里可以加个判断,如果变化前的值和变化后的值不一样才存——普通watch能拿到新旧值,所以可以加,watchEffect不行,这就是区别。
watchPostEffect,这个是Vue3.2之后才有的,它和watchEffect的区别在于执行时机,watchEffect是在DOM更新之前执行的,而watchPostEffect是在DOM更新之后执行的,这个什么时候用呢?比如你监听Pinia里的某个列表数据,列表数据变了之后,你需要获取DOM元素的高度,比如虚拟滚动需要计算容器的高度,或者聊天框需要自动滚动到底部——这时候如果用watchEffect,DOM还没更新,你拿到的高度就是旧的,自动滚动也不会生效;这时候就必须用watchPostEffect了,哦对了,还有一个watchSyncEffect,但这个用得特别少,它是在响应式数据变化的同步阶段执行的,连Vue的更新队列都没进,一般只有在处理一些特殊的第三方库的时候才会用到,这里就不多说了。
第三个前提:Pinia的action本身不会触发watch,除非它修改了state
很多新手会误以为“我调用了store里的action,watch就会触发”,这是完全错误的,watch监听的是响应式数据的变化,不管你是通过什么方式修改的——直接在组件里修改store.state,还是在store的action里修改,或者在getter里修改?不对,getter不能修改state哦,官方明确说了getter是只读的,用来计算衍生状态的,如果你在getter里修改state,会报错的,所以只有当你修改了store里的state或者getter的依赖项(也就是state)的时候,watch才会触发。
那如果我想监听某个action被调用了怎么办?这时候watch就没用了,你可以用Pinia的订阅机制——也就是store.$onAction,这个可以监听所有action的调用,包括调用前、调用成功后、调用失败后,还能拿到action的名称、参数、返回值之类的信息,不过这个不属于今天的主题,今天主要讲watch监听Pinia状态,所以$onAction就简单提一下,大家有兴趣可以自己去看。
踩过的5个大坑,90%的开发者都遇到过
刚才说了三个前提,现在来说说我刚上手的时候踩过的,还有群里看到好多人踩过的5个大坑,这些坑真的很容易浪费你好几个小时的时间。
坑一:直接解构Pinia的state属性,导致watch失效
这个坑真的是高频中的高频,我刚从Vuex转Pinia的时候,第一个踩的就是这个,当时我想监听用户的昵称,就直接这样写了:
import { watch } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// ❌ 直接解构state的基本类型属性,变成了普通值,不是响应式的
const { nickname } = userStore
watch(nickname, (newVal) => {
console.log('昵称变了:', newVal)
})
结果我在组件里或者store的action里修改了nickname,watch根本没反应,后来才想明白,Vue的响应式是基于Proxy的(或者Object.defineProperty,在兼容模式下),当你解构一个reactive对象的基本类型属性的时候,Proxy的拦截就失效了,解构出来的就是一个普通的JavaScript值,当然不会触发watch了。
那怎么解决这个问题?有两个方法:
- 用storeToRefs包裹整个store实例,然后再解构——这个是官方推荐的方法,
storeToRefs是Pinia专门提供的一个API,它会把store里的所有state和getters都转换成ref,同时保留它们的响应性,而且不会影响actions的引用,代码就变成这样:import { watch, storeToRefs } from 'vue' // 哦不对,storeToRefs是Pinia的,不是Vue的! // ❌ 刚才又写错了,storeToRefs在'pinia'包里 import { watch } from 'vue' import { storeToRefs } from 'pinia' import { useUserStore } from '@/stores/user'
const userStore = useUserStore() // ✅ 用storeToRefs包裹,然后解构state和getters const { nickname, userInfo, isVip } = storeToRefs(userStore)
// 监听基本类型的ref watch(nickname, (newVal) => { console.log('昵称变了:', newVal) })
// 监听引用类型的ref,要开deep watch(userInfo, (newVal) => { console.log('用户信息变了:', newVal) }, { deep: true })
// actions不用storeToRefs,直接用userStore里的就行 const login = userStore.login
这里要特别注意,`storeToRefs`只会处理state和getters,不会处理actions,所以解构的时候如果把actions也解出来的话,会报错的,所以actions最好还是直接从userStore里调用,不要解构。
2. **用getter函数的形式写watch的第一个参数,不解构**——这个方法也可以,而且有时候更方便,比如你只需要监听一个属性,不想用storeToRefs的话,代码就变成这样:
```javascript
import { watch } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// ✅ 用getter函数的形式监听基本类型
watch(() => userStore.nickname, (newVal) => {
console.log('昵称变了:', newVal)
})
// ✅ 用getter函数的形式监听引用类型,开deep
watch(() => userStore.userInfo, (newVal) => {
console.log('用户信息变了:', newVal)
}, { deep: true })
这两个方法哪个更好?其实要看场景:如果你在组件里需要用到很多store里的state和getters,那用storeToRefs包裹然后解构会更方便,代码也更整洁;如果你只需要用到一两个,那用getter函数的形式就够了。
坑二:监听引用类型的state时没开deep,导致嵌套属性变化不触发watch
刚才的代码里已经提到了这个问题,比如userInfo是一个对象,里面有age、gender这些属性,如果你直接这样写:
// 不管是用getter函数的形式,还是用storeToRefs的ref
watch(() => userStore.userInfo, (newVal) => {
console.log('用户信息变了:', newVal)
})
那只有当你把整个userInfo对象替换掉的时候,比如userStore.userInfo = { name: '李四', age: 20 },watch才会触发;如果你只是修改userInfo里的某个属性,比如userStore.userInfo.age = 21,watch是不会触发的,因为watch默认是浅层监听的,它只会监听引用类型的引用变化,不会监听内部的属性变化。
那怎么解决?很简单,加个deep: true的配置项就可以了,就像刚才的代码那样,不过加了deep之后,性能会有所下降,因为Vue需要递归遍历整个对象,监听所有的属性变化,所以如果你的引用类型对象特别大,嵌套特别深,而且你只需要监听其中的某几个属性,那最好不要加deep,而是直接监听那几个具体的属性,
// ✅ 直接监听嵌套的具体属性,不需要deep,性能更好
watch(() => userStore.userInfo.age, (newVal) => {
console.log('年龄变了:', newVal)
})
这样的话,只有age变化的时候才会触发watch,不管userInfo里的其他属性怎么变,都不会触发,性能会好很多。
坑三:监听多个源的时候,用数组传参但没注意顺序
Vue3的watch支持监听多个源,只要把它们放在一个数组里传给第一个参数就行,
import { watch, storeToRefs } from 'pinia'
import { useUserStore, useCartStore } from '@/stores'
const userStore = useUserStore()
const cartStore = useCartStore()
const { nickname, isVip } = storeToRefs(userStore)
const { cartCount } = storeToRefs(cartStore)
// ✅ 监听多个源,数组传参
watch([nickname, isVip, cartCount], ([newNickname, newIsVip, newCartCount], [oldNickname, oldIsVip, oldCartCount]) => {
console.log('新值:', newNickname, newIsVip, newCartCount)
console.log('旧值:', oldNickname, oldIsVip, oldCartCount)
})
这里要注意的是,新值和旧值的数组顺序,必须和监听源的数组顺序完全一致——第一个新值对应第一个监听源,第一个旧值也对应第一个监听源,以此类推,如果你顺序搞反了,拿到的新旧值就会出错,这个也是很容易犯的小错误,不过细心一点就可以避免。
坑四:watch的回调函数里修改了监听的源,导致死循环
这个坑不管是监听普通的Vue响应式数据,还是监听Pinia状态,都很容易踩,比如你监听了用户的昵称,然后在回调函数里又修改了昵称,代码就变成这样:
watch(() => userStore.nickname, (newVal) => {
console.log('昵称变了:', newVal)
// ❌ 修改了监听的源,导致死循环
userStore.nickname = newVal + 'a'
})
这时候只要你修改一次昵称,watch就会触发,然后又修改昵称,又触发,又修改……无限循环下去,浏览器会直接卡死。
那怎么解决?你要搞清楚,你为什么要在watch的回调函数里修改监听的源?如果是想对监听的源做一些格式化的处理,比如把昵称转成大写,那最好不要用watch,而是用getter——getter本来就是用来做衍生状态的,而且是只读的,不会修改原有的state,性能也比watch好。
// 在Pinia store的getters里定义
export const useUserStore = defineStore('user', {
state: () => ({
nickname: 'zhangsan'
}),
getters: {
uppercaseNickname: (state) => state.nickname.toUpperCase()
}
})
然后在组件里直接用uppercaseNickname就行,不用watch。
如果必须要在watch的回调函数里修改监听的源,那一定要加一个判断条件,只有当满足某个条件的时候才修改,
watch(() => userStore.nickname, (newVal) => {
console.log('昵称变了:', newVal)
// ✅ 加判断条件,避免死循环
if (!newVal.endsWith('a')) {
userStore.nickname = newVal + 'a'
}
})
这样的话,只要昵称已经以'a'结尾了,就不会再修改了,也就不会死循环了。
坑五:组件卸载后watch没有自动清理,导致内存泄漏
很多新手会担心这个问题:“我在组件里写了watch,组件卸载之后会不会还在监听,导致内存泄漏?”其实不用担心,Vue3里的watch(包括watchEffect、watchPostEffect)是和当前组件的生命周期绑定的——当组件卸载的时候,Vue会自动把这些watch清理掉,不需要你手动去清理。
那什么时候需要手动清理watch呢?有两种情况:
- 你是在组件的setup函数之外创建的watch——比如在普通的JavaScript文件里创建的watch,这时候没有和任何组件绑定,所以组件卸载的时候不会自动清理,你需要手动保存watch的返回值,然后在合适的时候调用它来清理,
// 在普通的JS文件里 import { watch } from 'vue' import { useUserStore } from '@/stores/user'
const userStore = useUserStore() // 保存watch的返回值,这是一个清理函数 const unwatch = watch(() => userStore.nickname, (newVal) => { console.log('昵称变了:', newVal) })
// 当你不需要监听的时候,手动调用unwatch() unwatch()
**你是在组件的setup函数里,但在某个异步操作之后才创建的watch**——比如在一个setTimeout里创建的watch,这时候虽然是在组件的setup函数里,但创建的时候可能组件已经卸载了?不对,setTimeout是宏任务,组件卸载是同步的,所以如果setTimeout的延迟时间很长,组件已经卸载了,但setTimeout还没执行,这时候创建的watch就不会和组件绑定,所以需要手动清理吗?或者有没有更好的方法?其实有的,你可以用Vue提供的`onUnmounted`钩子,在组件卸载的时候手动清理watch,或者用`getCurrentInstance`获取当前组件的实例,然后在实例上挂载清理函数,不过这种情况比较少见,一般只要你是在组件的setup函数的同步代码里创建的watch,就不需要手动清理。
哦对了,还有一个情况:Pinia的`store.$subscribe`——这个是用来订阅state的变化的,它不会和组件的生命周期绑定,所以如果你在组件里用了`$subscribe`,一定要在`onUnmounted`里手动清理,不然会导致内存泄漏,`$subscribe`和watch有什么区别?`$subscribe`不管你通过什么方式修改state,哪怕是直接在devtools里修改,都会触发,而且它拿到的是整个state的变化补丁(patch),可以知道哪些属性被修改了,修改前和修改后的值是什么,这个在做状态持久化的时候特别有用,比如pinia-plugin-persistedstate这个插件,就是用`$subscribe`来实现的,不过这个也不属于今天的主题,简单提一下就行。
## 3个高效监听的技巧,提升代码性能和可维护性
刚才说了三个前提和五个大坑,现在来说说三个高效监听的技巧,这些技巧可以让你的代码性能更好,也更容易维护。
### 技巧一:优先用getter代替watch做衍生状态
刚才在坑四里已经提到过了,getter本来就是用来做衍生状态的,它的性能比watch好很多——因为getter是缓存的,只有当它的依赖项发生变化的时候才会重新计算,而watch只要监听的源发生变化就会触发回调函数,不管回调函数里的逻辑是不是需要执行,而且getter是只读的,不会修改原有的state,代码更安全。
那什么时候用getter?什么时候用watch?这里有一个简单的判断标准:
- 如果你的需求是**根据现有状态计算出一个新的状态,而且这个新状态不需要做其他操作(比如调用接口、修改DOM、存localStorage)**,那就用getter。
- 如果你的需求是**根据现有状态的变化做一些副作用操作(比如调用接口、修改DOM、存localStorage、跳转路由)**,那就用watch或者watchEffect。
举个例子,比如你有一个购物车的store,里面有cartList(商品列表),每个商品有price(单价)和count(数量),你需要计算购物车的总价——这个就是衍生状态,用getter最合适:
```javascript
export const useCartStore = defineStore('cart', {
state: () => ({
cartList: [
{ id: 1, name: '苹果', price: 5, count: 2 },
{ id: 2, name: '香蕉', price: 3, count: 3 }
]
}),
getters: {
totalPrice: (state) => {
return state.cartList.reduce((sum, item) => sum + item.price * item.count, 0)
}
}
})
然后在组件里直接用totalPrice就行,不用watch。
再举个例子,比如你有一个搜索的store,里面有searchKeyword(搜索关键词),你需要当搜索关键词变化的时候,延迟500毫秒调用搜索接口——这个就是副作用操作,用watch最合适:
import { watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useSearchStore } from '@/stores/search'
import { debounce } from 'lodash-es'
const searchStore = useSearchStore()
const { searchKeyword } = storeToRefs(searchStore)
// 定义搜索接口的函数,用lodash的debounce做防抖
const handleSearch = debounce(async (keyword) => {
if (!keyword) return
const res = await fetchSearchResult(keyword)
searchStore.searchResult = res.data
}, 500)
// 监听searchKeyword的变化,调用handleSearch
watch(searchKeyword, (newVal) => {
handleSearch(newVal)
})
这里用了lodash的debounce做防抖,避免搜索关键词变化太快的时候调用太多次接口,这个是搜索场景下的常用优化,大家可以参考一下。
用computed + watch的组合,避免deep监听
刚才在坑二里说过,加deep会影响性能,如果你的引用类型对象特别大,嵌套特别深,那可以用computed + watch的组合,先把你需要监听的那部分数据用computed计算出来,然后再监听这个computed,这样就不需要加deep了,性能会好很多。
举个例子,比如你有一个用户信息的store,里面的userInfo嵌套特别深:
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: {
base: {
name: '张三',
age: 20,
contact: {
phone: '13800138000',
email: 'zhangsan@example.com',
address: {
province: '广东省',
city: '深圳市',
district: '南山区'
}
}
},
vip: {
level: 1,
expireTime: '2025-12-31'
}
}
})
})
现在你需要监听userInfo里的city变化,这时候你可以直接监听() => userStore.userInfo.base.contact.address.city,这个不需要加deep,性能也很好;但如果你需要监听userInfo里的所有contact信息变化,包括phone、email、address,这时候如果你直接监听() => userStore.userInfo.base.contact,就要加deep,因为contact是一个引用类型;这时候你可以用computed先把contact计算出来,然后再监听这个computed?不对,computed如果返回的是引用类型,还是要加deep啊?哦,不对,如果你用computed返回的是一个新的对象,只有当contact里的任何属性变化的时候,computed才会返回新的对象?不对,computed默认也是浅层比较的?哦,等一下,我刚才搞错了,不管是computed还是watch,默认都是浅层比较的,那用computed + watch的组合怎么避免deep?哦,对了,你可以用computed返回一个包含你需要监听的所有属性的新对象,然后用watch的flush: 'sync'?不对,不是,哦,你可以用computed的get和set?不对,可能我刚才的例子举得不好,换一个例子:比如你有一个列表的store,里面的list是一个数组,每个元素是一个对象,你需要监听列表里所有元素的price变化,计算出总价的变化,这时候你可以用getter计算总价,然后监听这个getter,因为getter的依赖项是所有元素的price,只要任何一个price变化,getter就会重新计算,然后监听getter的话,就会触发,不需要加deep,哦,对,这个例子更合适:
// 刚才的购物车getter例子,监听totalPrice这个getter,不需要加deep
watch(() => cartStore.totalPrice, (newVal) => {
console.log('总价变了:', newVal)
// 比如把总价存到localStorage里
localStorage.setItem('cartTotalPrice', newVal)
})
这样的话,不管是哪个商品的price变了,还是count变了,totalPrice都会重新计算,然后watch就会触发,不需要加deep,性能很好。
用watchEffect的onCleanup清理副作用
不管是watch还是watchEffect,都提供了一个onCleanup的函数,用来清理上一次执行的副作用,这个在处理异步操作的时候特别有用,比如取消上一次的请求,或者清除上一次的定时器。
举个例子,比如刚才的搜索场景,你不用lodash的debounce,而是自己用setTimeout实现防抖,这时候就可以用onCleanup清除上一次的定时器:
import { watchEffect } from 'vue'
import { storeToRefs } from 'pinia'
import { useSearchStore } from '@/stores/search'
const searchStore = useSearchStore()
const { searchKeyword } = storeToRefs(searchStore)
watchEffect((onCleanup) => {
const keyword = searchKeyword.value
if (!keyword) {
searchStore.searchResult = []
return
}
// 设置定时器,延迟500毫秒调用搜索接口
const timer = setTimeout(async () => {
const res = await fetchSearchResult(keyword)
searchStore.searchResult = res.data
}, 500)
// 注册清理函数,清除上一次的定时器
onCleanup(() => {
clearTimeout(timer)
})
})
这样的话,如果搜索关键词在500毫秒内又变化了,onCleanup就会清除上一次的定时器,然后设置新的定时器,只有当搜索关键词稳定500毫秒之后,才会调用搜索接口,实现了防抖的效果,而且不需要引入lodash,更轻量。
再举个例子,比如你需要监听某个状态的变化,然后发起一个请求,但是如果在请求还没回来的时候,状态又变化了,那上一次的请求就没用了,这时候可以用AbortController来取消上一次的请求:
import { watchEffect } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { userId } = storeToRefs(userStore)
watchEffect((onCleanup) => {
const id = userId.value
if (!id) return
// 创建AbortController
const controller = new AbortController()
const signal = controller.signal
// 发起请求,传入signal
const fetchUserDetail = async () => {
try {
const res = await fetch(`/api/user/${id}`, { signal })
const data = await res.json()
userStore.userDetail = data
} catch (error) {
// 如果是请求被取消的错误,就不处理
if (error.name !== 'AbortError') {
console.error('请求失败:', error)
}
}
}
fetchUserDetail()
// 注册清理函数,取消上一次的请求
onCleanup(() => {
controller.abort()
})
})
这样的话,如果userId在请求还没回来的时候又变化了,onCleanup就会取消上一次的请求,避免浪费网络资源,也避免上一次的请求回来之后覆盖掉新的请求结果。
Vue3 watch监听Pinia状态的正确打开方式
我们来总结一下Vue3 watch监听Pinia状态的正确打开方式:
- 搞清楚三个核心前提:Pinia的store实例是响应式的,但要监听部分内容最好用getter函数的形式或者storeToRefs;区分watch、watchEffect、watchPostEffect的执行时机和特点,结合场景选对;只有修改了state才会触发watch,action本身不会。
- 避开五个大坑:不要直接解构Pinia的state属性,要用storeToRefs或者getter函数;监听引用类型的嵌套属性时要么直接监听具体属性,要么加deep,但要注意性能;监听多个源的时候注意新旧值的顺序;不要在watch的回调函数里随便修改监听的源,必要时加判断条件;组件setup函数里的同步watch不需要手动清理,其他情况要注意。
- 掌握三个高效技巧:优先用getter代替watch做衍生状态;用computed + getter + watch的组合避免deep监听;用onCleanup清理副作用,处理异步操作更安全。
不管是监听普通的Vue响应式数据,还是监听Pinia状态,核心都是一样的:搞清楚响应式的原理,区分不同API的特点,结合场景选择合适的方式,避免不必要的性能损耗和错误,如果你还有其他关于Vue3 watch监听Pinia状态的问题,欢迎在评论区留言讨论,我们一起交流学习。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



