vue 又发布了一个小版本:v3.5 。各路大神也都在写文章介绍,自我感觉 这应该是全网最详细的Vue3.5版本解读 就挺全的,我也是按照介绍一一尝试一遍,然后发现了几个小坑:props的解构以及监听的暂停、恢复。
解构 props 的小坑
在vue3.3 版本里面就推出了这个功能,只是需要手动在config里面做设置,到了 vue 3.5 正式转正,不需要在做设置了。
什么坑呢?还是举个例子说明。
使用 toRefs 解构 props
以前想要解构 props 可以使用 toRefs:
// 设置一个函数
const foo1 = (v: any) => {
// 函数内部监听传入的参数,可以有响应性
watch (v, (val) => {
console.log('函数内部监听 name:', val)
})
}
// 定义 props
const props = defineProps({name: String})
// 使用 toRefs 解构
const { name } = toRefs(props)
console.log(name) // name 是 ref
// 函数传参
foo1(name) // name 其实是一个 ref,所以传参后,依然有响应性
toRefs 解构后,得到的是一个 ref:
ObjectRefImpl {
_object: Proxy,
_key: "name",
_defaultValue: undefined,
__v_isRef: true,
_value: undefined
}
所以这个 name 本身是有响应性的,函数传参后依旧可以使用 watch 对其进行监听。
props 直接解构
我们再来看看3.5 的 props 的解构:
// 直接对 props 解构
const { name } = defineProps({name: String})
console.log(name) // 是 string
// 直接监听 name,有响应
watch (() => name, (val) => {
console.log('直接监听 name :', val)
})
// 函数传参
foo1(name) // 传递的不是 props ,也不是 ref,而是 string
- 直接监听 name ,可以有响应性。
- 函数传参后,监听参数,没有响应性。
如果按照 toRefs 的思维方式去思考,函数传参后,也应该有响应性,但是,现实并非如此。
我们看看“翻译”后的代码,就很明显了。
setup(__props, { expose: __expose }) {
__expose();
console.log(__props.name); // 代码里的 name
watch(() => __props.name, (val) => { // 代码里的 name
console.log("直接监听 name", val);
});
const foo1 = (v) => { // 传入的是 string
watch(() => v, (val) => {
console.log("函数内部监听 name", val);
});
};
foo1(__props.name); // 传入的是 __props.name 的值
解构后的 name,被翻译成了 __props.name,后面直接使用 __props.name ,自然会有响应性。
但是调用函数的时候呢,foo1(__props.name),这样看就很明显了,传递的是 __props.name的值 ,这个值是一个 string,函数收到string,当然没有响应性。
如果不解构,直接用 props.xxx 的话,也就不会有这个错觉了。所以,这个算不算是一个小坑?
watch 的 pause、resume 的小坑(还有stop)
暂停、恢复:在暂停期间不进行监听,这个没有问题,那么恢复之后呢?
举个 watchEffect 的例子:
const count = ref(0)
const runner2 = watchEffect(() => {
console.log('watchEffect监听:', count.value)
})
<button @click="count++">count++</button>
<button @click="runner2.pause()">暂停</button>
<button @click="runner2.resume()">恢复</button>
代码很简单:
- 我们按 count++ ,会不断打印:1、2...;
- 如果按暂停,那么按 count++ 不会打印,到这都没有问题;
- 然后我们按恢复,大家猜猜会如何?会立即打印一个数字,比如 6 。(只打印最后一次的数值,中间的不会打印出来)
不知道这和大家预想的是否一致,反正我觉得,按恢复后,不应该立即执行。
或者说,如果我想实现:按恢复后,不立即执行,要如何调整代码?(目前没有想到办法)
举个 watch 的例子
const count = ref(0)
const runner3 = watch(count, (newValue, oldValue)=> {
console.log(`watch监听,新值:${newValue};旧值:${oldValue}`)
})
<button @click="count++">count++</button>
<button @click="runner3.pause()">暂停</button>
<button @click="runner3.resume()">恢复</button>
运行结果:
watch监听,新值:1;旧值:0
watch监听,新值:2;旧值:1
watch监听,新值:3;旧值:2
watch监听,新值:7;旧值:3
暂停后,旧值没有同步,恢复后用暂停时的旧值和新值对比,发现不一样,于是触发回调函数。
这个,不知道和大家的预想是否一致。
怎么说呢,可能符合某些需求,但是不符合我的一个需求。
前面写了一个列表数据的文章: 【vue3】compositionAPI的最佳实践:列表篇,里面用到了两个 watch:
- 一个监听查询条件,
- 一个监听翻页的页号。
如果查询条件变化了,那么翻页的 watch 就应该暂时失效,否则会重复向后端申请数据。
如果使用这种方式的话,恢复监听后,翻页的 watch 后依然会被执行,这样和我的预期就不一样了。
我的应用场景是这样的:
- 查询条件变更,触发 watch,更新数据。然后页号设置为 1 。
- 翻页时页号变更,触发 watch,更新数据。
上面的逻辑没啥问题,只是如果先翻到第二页,然后设置查询条件的时候,会更新两次数据:查询一次;查询里面修改页号,又更新一次。
所以,我期望,查询条件变更的时候,翻页的 watch 暂停,不去更新数据。
但是,恢复后,依然会用 2 和 1 做对比,于是,数据又被更新一次。
无语了。
想过判断新旧值,但是,这不对呀。
stop (补充)
看了看源码,发现还有一个 stop 的用法,停止监听后,就一直停止,恢复不过来了。
const stop = () => {
runner.stop() // 停止监听,不能恢复
}
const pause = () => {
runner.pause() // 暂停后可以恢复
}
const resume = () => {
runner.resume()
}
修改了一种警告
这是无意间发现的,以前用递归子组件的方式写了一个n级菜单,虽然运行正常,但是会出现警告:
[Vue warn]: Vue received a Component that was made a reactive object.
This can lead to unnecessary performance overhead and should be avoided
by marking the component with `markRaw` or using `shallowRef` instead of `ref`.
Component that was made reactive: {name: "Document", render: ƒ}
看了好久也不知道要如何改,如果加上 markRaw,那么菜单如何动态更新?
这次升级后发现,这种警告消失了。
另外,又增加了一致警告:当 watch 监听的对象没有响应性的时候,会出现警告!
这种警告就比较有意义,避免一些莫名其妙的情况
小结
这次更新版本,主要是内部性能优化,从使用的角度来看,似乎吸引度不高。
增加的一些新功能(语法糖),没有想到有实际的应用场景,当然,可能是我的脑洞不够大。