浅层响应式代理:shallowReactive
有的时候,我们并不需要嵌套属性也具有响应性,这时可以使用shallowReactive 来获得浅层的响应式代理,这种方式只拦截自己的属性的操作,不涉及嵌套的对象属性的操作。
const personShallowReactive = shallowReactive({
name: 'jykShallowReactive',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
// 查看 shallowReactive 实例结构
console.log('shallowReactive', objectShallowReactive)
// 获取嵌套对象属性
const contacts = objectShallowReactive.contacts
// 因为浅层代理,所以没有响应性
console.log('contacts属性:', contacts)
// 获取简单类型的属性
let name = objectShallowReactive.name
// 因为浅层代理且简单类型,所以失去响应性
console.log('name属性:', name)
shallowReactive 也是用 Proxy 实现响应性的,而单独使用contacts属性并没有响应性,因为 shallowReactive 是浅层代理,所以不会让嵌套对象获得响应性。
注意:objectShallowReactive.contacts.QQ = 123 ,这样修改属性也是没有响应性的。
单独使用的属性的形式:
嵌套对象和name属性,都没有变成响应式。
做一个不允许响应的标记:markRaw
有的时候我们不希望js对象变成响应式的,这时我们可以用markRaw 做一个标记,这样即使使用 reactive 也不会变成响应式。
如果确定某些数据是不会变化的,那么也就不用变成响应式,这样可以节省一些不必要的性能开销。
// 标记js对象
const object = markRaw({
name: 'jyk',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
// 试图对标记的对象做相应性代理
const retObject2 = reactive(object)
// 使用对象的属性做相应性代理
const retObject1 = reactive({
name: object.name
})
console.log('作为初始值:', retObject1) // 无法变成响应性代理
console.log('无法变成响应式:', retObject2) // 可以变成响应性代理
运行结果:
做标记后的js对象作为参数,不会变成响应式,但是使用属性值作为参数,还是可以变成响应式。
那么哪些地方可以用到呢?我们可以在给组件设置(引用类型的)属性的时候使用,默认情况下组件的属性都是自带响应性的,但是如果父组件里设置给子组件的属性值永远不会发生变化,那么还变成响应式的话,就有点浪费性能的嫌疑了。
如果想节约一下的话,可以在父组件设置属性的时候加上markRaw标记。
深层只读响应式代理:readonly
有的时候虽然我们想得到一个响应式的代理,但是只想被读取,而不希望被修改(比如组件的props,组件内部不希望被修改),那么这时候我们可以用readonly。
readonly可以返回object、reactive或者ref的深层只读代理,我们来分别测试一下:
// object的只读响应代理
const objectReadonly = readonly(person)
// reactive 的只读响应代理
const reactiveReadonly = readonly(objectReactive)
// 查看 readonly 实例结构
console.log('object 的readonly', objectReadonly)
console.log('reactive 的readonly', reactiveReadonly)
// 获取嵌套对象属性
const contacts = reactiveReadonly.contacts
console.log('contacts属性:', contacts) // 因为深层响应,所以依然有响应性
// 获取简单类型的属性
let name = reactiveReadonly.name
console.log('name属性:', name) // 属性是简单类型的,所以失去响应性
运行结果:
- Handler,明显拦截的函数变少了,set的参数也变少了,点进去看源码,也仅仅只有一行返回警告的代码,这样实现拦截设置属性的操作。
- Target,指向object。
运行结果:
- Handler,这部分是一样的。
- Target,指向的不是object,而是一个Proxy代理,也就是reactive。
浅层只读响应代理:shallowReadonly
和readonly相对应,shallowReadonly是浅层的只读响应代理,和readonly的使用方式一样,只是不会限制嵌套对象只读。
// object 的浅层只读代理
const objectShallowReadonly = shallowReadonly(person)
// reactive 的浅层只读代理
const reactiveShallowReadonly = shallowReadonly(objectReactive)
shallowReadonly的结构和 readonly 的一致,就不贴截图了。
获取原型:toRaw
toRaw 可以获取 Vue 建立的代理的原型对象,但是不能获取我们自己定义的Proxy的实例的原型。
toRaw大多是在Vue内部使用,目前只发现在向indexedDB里面写入数据的时候,需要先用 toRaw 取原型,否则会报错。
// 获取reactive、shallowReactive、readonly、shallowReadonly的原型
console.log('深层响应的原型', toRaw(objectReactive))
console.log('浅层响应的原型', toRaw(objectShallowReactive))
console.log('深层只读的原型', toRaw(objectReadonly))
console.log('浅层只读的原型', toRaw(objectShallowReadonly))
运行结果都是普通的object,就不贴截图了。
类型判断
Vue提供了三个用于判断类型的函数:
isProxy:判断对象是否是Vue建立的Proxy代理,包含reactive、readonly、shallowReactive和shallowReadonly创建的代理,但是不会判断自己写的Proxy代理。
isReactive:判断是否是reactive创建的代理。如果readonly的原型是reactive,那么也会返回true。
isReadonly:判断是否是readonly、shallowReadonly创建的代理。这个最简单,只看代理不看target。
我们用这三个函数判断一下我们上面定义的这些Proxy代理,看看结果如何。
我们写点代码对比一下:
const myProxyObject = myProxy({title:'222', __v_isReactive: false})
console.log('myProxyObject', myProxyObject)
const myProxyReactive = myProxy(objectReactive)
console.log('myProxyReactive', myProxyReactive)
// 试一试 __v_isReadonly
console.log('objectReactive', objectReactive)
console.log('__v_isReadonly'
, objectReactive.__v_isReadonly
, objectReactive.__v_isReactive
)
return {
obj: { // js对象
check1: isProxy(person),
check2: isReactive(person),
check3: isReadonly(person)
},
myproxy: { // 自己定义的Proxy object
check1: isProxy(myProxyObject),
check2: isReactive(myProxyObject),
check3: isReadonly(myProxyObject)
},
myproxyReactive: { // 自己定义的Proxy reactive
check1: isProxy(myProxyReactive),
check2: isReactive(myProxyReactive),
check3: isReadonly(myProxyReactive)
},
// 深层响应 reactive(object)
reto: { // reactive(object)
check1: isProxy(objectReactive),
check2: isReactive(objectReactive),
check3: isReadonly(objectReactive)
},
// 浅层响应 参数:object
shallowRetObj: {
check1: isProxy(objectShallowReactive),
check2: isReactive(objectShallowReactive),
check3: isReadonly(objectShallowReactive)
},
// 浅层响应 参数:reactive
shallowRetRet: {
check1: isProxy(objectShallowReactive),
check2: isReactive(objectShallowReactive),
check3: isReadonly(objectShallowReactive)
},
// 深层只读,参数 object =======================
readObj: { // readonly object
check1: isProxy(objectReadonly),
check2: isReactive(objectReadonly),
check3: isReadonly(objectReadonly)
},
// 深层只读,参数 reactive
readRet: { // readonly reactive
check1: isProxy(reactiveReadonly),
check2: isReactive(reactiveReadonly),
check3: isReadonly(reactiveReadonly)
},
// 浅层只读 参数:object
shallowReadObj: {
check1: isProxy(objectShallowReadonly),
check2: isReactive(objectShallowReadonly),
check3: isReadonly(objectShallowReadonly)
},
// 浅层只读 参数:reactive
shallowReadRet: {
check1: isProxy(reactiveShallowReadonly),
check2: isReactive(reactiveShallowReadonly),
check3: isReadonly(reactiveShallowReadonly)
},
person
}
对比结果:
总结一下:
isReadonly 最简单,只有readonly、shallowReadonly建立的代理才会返回 true,其他的都是 false。
isProxy也比较简单,Vue建立的代理才会返回true,如果是自己定义的Proxy,要看原型是谁,如果原型是 reactive(包括其他三个)的话,也会返回true。
isReactive就有点复杂,reactive 建立的代理会返回 true,其他的代理(包含自己写的)还要看一下原型,如果是 reactive 的话,也会返回true。
判断依据
那么这三个函数是依据什么判断的呢?自己做的 Proxy 无意中监控到了“__v_isReactive”,难道是隐藏属性?测试了一下,果然是这样。
myProxy({title:'测试隐藏属性', __v_isReactive: true}),这样定义一个实例,也会返回true。
reactive直接赋值的方法
使用的时候我们会发现一个问题,如果直接给 reactive 的实例赋值的话,就会“失去”响应性,这个并不是因为 reactive 失效了,而是因为 setup 只会运行一次,return也只有一次给模板提供数据(地址)的机会,模板只能得到一开始提供的 reactive 的地址,如果后续直接对 reactive 的实例赋值操作,会覆盖原有的地址,产生一个新的Proxy代理地址,然而模板并不会得到这个新地址,还在使用“旧”地址,因为无法获知新地址的存在,所以模板不会有变化。
那么就不能直接赋值了吗?其实还是有方法的,只需要保证地址不会发生变化即可。
对象的整体赋值的方法。
有请 ES6 的 Object.assign 登场,这个方法是用来合并两个或者多个对象的属性的,如果属性名称相同后面的属性会覆盖前面的属性。所以大家在使用的时候要谨慎使用,确保两个对象的属性就兼容的,不会冲突。
代码如下:
Object.assign(objectReactive, {name: '合并', age: 20, newProp: '新属性'})
数组的整体赋值的方法。
数组就方便多了,可以先清空再 push 的方式,代码如下:
// retArray.length = 0 // 这里清空的话,容易照成闪烁,所以不要急
setTimeout(() => {
const newArray = [
{ name: '11', age: 18 },
{ name: '22', age: 18 }
]
// 等到这里再清空,就不闪烁了。
retArray.length = 0
retArray.push(...newArray)
}, 1000)
var 和 let、const
ES6 新增了 let 和 const,那么我们应该如何选择呢?
简单的说,var不必继续使用了。
let 和 const 的最大区别就是,前者是定义“变量”的,后者是定义“常量”的。
可能你会觉得奇怪,上面的代码都是用const定义的,但是后续代码都是各种改呀,怎么就常量了?其实const判断的是,地址是否改变,只要地址不变就可以。
对于基础类型,值变了地址就变了;而对于引用类型来说,改属性值的话,对象地址是不会发生变化的。
而 const 的这个特点整合可以用于保护 reactive 的实例。由Vue的机制决定,reactive的实例的地址是不可以改变的,变了的话模板就不会自动更新,const可以确保地址不变,变了会报错(开发阶段需要eslint支持)。
于是const和reactive(包括 ref 等)就成了绝配。
源码:
GitHub总是上不去,所以搬到gitee了。
https://gitee.com/naturefw/nf-vue-cdn/tree/master/cdn/project-compositionapi
在线演示:
https://naturefw.gitee.io/nf-vue-cdn/cdn/project-compositionapi/