vue 提供了响应性的功能,用起来非常方便,只是 compositionAPI 把响应性提取出来之后,就出现了各种各样的小问题,在这里汇总一下。
因为描述起来比较绕口,所以先做几个约定:
reactive 非常好用,只是不能整体赋值,否则会失去响应性,官方不想想如何弥补,而是一刀切的推荐使用 ref,其实 ref 一样有坑。
还是先看看 reactive 的情况,举例说明:
// ✔ 正确用法,必须使用 const 定义
const foo1 = reactive ({name:'jyk'})
const foo2 = reactive ({name:'jyk'})
console.log(foo1 === foo2) // false,两个不同对象的代理,地址(指针)不同
// ✘ 错误用法 ,不要使用 let、var,整体赋值也没个阻拦(js会报错)。
let foo = reactive ({name:'jyk'})
const mychange = () => {
foo = reactive ({name:'jyk999'}) // 不响应
}
本质原因:setup 有一个 return,把各种响应性交给 template,这时候 template 会记录各种地址,在事件里面变更了地址,那么 template 呢?还在关注以前的地址呢,不知道新的地址,所以就无法响应了。
这一点很重要,vue 里面丢失响应性,大多都是这种原因。
reactive 是深层响应的,所以可以设计为多层对象,多层情况就更复杂了。
我们还是先看例子:
const more = reactive({
name: 'jyk',
info: {
name: '第二层'
}
})
// 默认深层监听,各种支持
watch(more, () => {
console.log('watch more:', more)
})
// 监听的是筐还是 【小米】?是【小米】!
watch(more.info, () => {
// 不会响应第二层的整体赋值
console.log('watch more.info:', more.info)
})
// 监听的是筐的变化,换新筐才能监听
watch(() => more.info, () => {
// 可以响应第二层的整体赋值
console.log('watch () => more.info:', more.info)
})
// 手动深层监听,筐、**小米**都能监听
watch(() => more.info, () => {
console.log('深层 watch () => more.info:', more.info)
},{deep:true})
const myChange2 = () => {
more.info = reactive({name:'jyk999'})
console.log('改变第二层后的 more:', more)
}
会不会丢失响应性?要看赋值方式,也要看监听方式。
这个就不多说了,尽量别解构就OK了。
如果上面的原理都理解了,那么 ref 的问题就清楚了,还是筐和小米的问题。
还是写代码举例:
const arr = ref([{
name: 'jyk'
}])
// A 只监听筐的变化,不管小米如何变
watch(arr, () => {
console.log('watch arr:', arr)
})
// B 手动深层监听,可以监听筐和小米
watch(arr, () => {
console.log('手动深层 watch arr:', arr)
},{deep:true})
// C 只监听 ref 自己,和筐、小米都无关
watch(() => arr, () => {
console.log('父组件: watch () => arr:', arr)
})
// D 一开始能监听苹果,当使用 ref.value = [] 后,就不能监听苹果了。
watch(arr.value, () => {
console.log('watch arr.value:', arr)
})
// E 手动监听筐,不是深层,相当于 A
watch(() => arr.value, () => {
console.log('watch () => arr.value:', arr)
})
// F 深层的监听,都有,相当于 B
watch(() => arr.value, () => {
console.log('手动深层 watch () => arr.value:', arr)
},{deep:true})
好吧,我承认,我自己都写蒙了,各种情况都要测试一下,看看实际效果如何。
不知道各种 watch 方法是否符合你们的预期。
watch(arr, () => {}, {deep:true}):
watch(arr,...)
等效于 watch(() => arr.value,...)
watch(arr.value, () => {})
watch(arr, () => {}):是深层监听吗?不是!(和 reactive 的情况不同)
.value
,在 js 代码里面需要使用 .value
。.value
。.value
,行为又不一致。不看 watch 源码的话,晕不晕?
...
if (isRef(source)) {
getter = () => source.value;
forceTrigger = isShallow(source);
} else if (isReactive(source)) {
getter = () => reactiveGetter(source);
forceTrigger = true; // 等效于 深层监听
} else if (isArray(source)) {
...
} else if (isFunction(source)) {
...
}
对第一个参数(source
)的类型进行各种判断。
还是看看代码:
const arr = ref([{
name: 'jyk'
}])
// 变更记录集
const change1 = () => {
arr.value = [{name: 'jyk9999'}]
}
// 修改数组的一个元素
const change2 = () => {
arr.value[0] = {name: 'jyk3333'}
}
子组件里的 template 是肯定有响应性的,而 props 是shallowReadonly,不是Readonly,也不是 ref,所以还是有一点点不一样的情况,我们来看看js代码里的 watch 的响应情况:
// 方案一
watch(props.listRef, () => {
console.log('----子组件: watch -- props.listRef:', props.listRef)
})
// 方案二
watch(props.listRef, () => {
console.log('----子组件: 手动深层:watch -- props.listRef:', props.listRef)
},{deep:true})
// 方案三
watch(() => props.listRef, () => {
console.log('----子组件: watch -- () => props.listRef:', props.listRef)
})
// 方案四
watch(() => props.listRef, () => {
console.log('----子组件: 手动深层:watch -- () => props.listRef:', props.listRef)
},{deep:true})
大家猜猜各种 watch 会如何响应性?
当父组件修改数组元素的时候,子组件的 watch 情况:
当父组件替换数组的时候,子组件的 watch 情况:
数组被替换后,再修改数组的元素,子组件的watch情况:
是不是有点乱?其实一开始我也没发现这么多种情况,因为我喜欢使用 reactive,还是前些日子有一位掘友问我,子组件的 watch 为啥没反应了?我问他具体写法,才发现这么多种情况。
最全面的还是方案四。
写了半天才反应过来,watch 也是一个函数,响应性作为第一个参数,传递前有响应性,那么函数里面接受的是什么呢?其实挺复杂的,所以 watch 内部做了一个长串的判断。
函数的参数,就不得不说说一个很古老的问题:
基础问题不多说了。
因篇幅问题不能全部显示,请点此查看更多更全内容