手拉手带你了解Vue3的reactive和相关函数
1
ES6的Proxy
// 定义一个函数,传入对象原型,然后创建一个Proxy的代理
const myProxy = (_target) => {
// 定义一个 Proxy 的实例
const proxy = new Proxy(_target, {
// 拦截 get 操作
get: function (target, key, receiver) {
console.log(`getting ${key}!`, target[key])
// 用 Reflect 调用原型方法
return Reflect.get(target, key, receiver)
},
// 拦截 set 操作
set: function (target, key, value, receiver) {
console.log(`setting ${key}:${value}!`)
// 用 Reflect 调用原型方法
return Reflect.set(target, key, value, receiver)
}
})
// 返回实例
return proxy
}
// 使用方法,是不是和reactive有点像?
const testProxy = myProxy({
name: 'jyk',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
console.log('自己定义的Proxy实例:')
console.log(testProxy)
// 测试拦截情况
testProxy.name = '新的名字' // set操作
console.log(testProxy.name) // get 操作
我们先来看一下运行结果:
Handler 可以看到我们写的拦截函数 get 和 set;
Target 可以看到对象原型。
注意:这里只是实现了 get 和 set 的拦截,并没有实现数据的双向绑定,模板也不会自动更新内容,Vue内部做了很多操作才实现了模板的自动更新功能。
2
用 Proxy 给 reactive 套个娃,会怎么样?
3
/**
* 用 Proxy定义一个 reactive 的套娃,实现可以监听任意属性变化的目的。(不包含嵌套对象的属性)
* @param {*} _target 要拦截的目标
* @param {*} callback 属性变化后的回调函数
*/
const myReactive = (_target, callback) => {
let _change = (key, value) => {console.log('内部函数')}
const proxy = new Proxy(_target, {
get: function (target, key, receiver) {
if (typeof key !== 'symbol') {
console.log(`getting ${key}!`, target[key])
} else {
console.log('getting symbol:', key, target[key])
}
// 调用原型方法
return Reflect.get(target, key, receiver)
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}:${value}!`)
// 源头监听
if (typeof callback === 'function') {
callback(key, value)
}
// 任意位置监听
if (typeof _target.__watch === 'function') {
_change(key, value)
}
// 调用原型方法
return Reflect.set(target, key, value, target)
}
})
// 实现任意位置的监听,
proxy.__watch = (callback) => {
if (typeof callback === 'function') {
_change = callback
}
}
// 返回实例
return proxy
}
get 这里要做一下 symbol 的判断,否则会报错。好吧,其实我们似乎不需要 console.log。 set 这里改了一下最后一个参数,这样模板就可以自己更新了。 设置 callback 函数,实现源头监听 设置一个回调函数,才能在拦截到set操作的时候,通知外部的调用者。只是这样只适合于定义实例的地方。那么接收参数的地方怎么办呢?
// 定义一个拦截reactive的Proxy
// 并且实现源头的监听
const myProxyReactive = myReactive(retObject,
((key, value) =>{
console.log(`ret外部获得通知:${key}:${value}`)
})
)
内部设置一个钩子函数 设置一个 _change() 钩子函数,这样接收参数的地方,可以通过这个钩子来得到变化的通知。
// 任意位置的监听
myProxyReactive.__watch((key, value) => {
console.log(`任意位置的监听:${key}:${value}`)
})
最后,好像没有watch的deep监听来的方法,那么问题又来了,为啥Vuex不用watch呢?或者悄悄的用了?
4
深层响应式代理:reactive
注意:返回的是 object 的代理,他们的地址是相同的,并没有对object进行clone(克隆),所以修改代理的属性值,也会影响原object的属性值;同时,修改原object的属性值,也会影响reactive返回的代理的属性值,只是代理无法拦截直接对原object的操作,所以模板不会有变化。
// js对象
const person = {
name: 'jyk',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
}
// person 的 reactive 代理 (验证地址是否相同)
const personReactive = reactive(person)
// js 对象 的 reactive 代理 (一般用法)
const objectReactive = reactive({
name: 'jykReactive',
age: 18,
contacts: {
QQ: 11111,
phone: 123456789
}
})
// 查看 reactive 实例结构
console.log('reactive', objectReactive )
// 获取嵌套对象属性
const contacts = objectReactive .contacts
// 因为深层响应,所以依然有响应性
console.log('contacts属性:', contacts)
// 获取简单类型的属性
let name = objectReactive.name
// name属性是简单类型的,所以失去响应性
console.log('name属性:', name)
Handler:可以看到Vue除重写set和get外,还重写了deleteProperty、has和ownKeys。 Target:指向一个Object,这是建立reactive实例时的对象。
注意:如果在模板里面使用{{personReactive.name}}的话,那么也是有响应性的,因为这种用法是获得对象的属性值,可以被Proxy代理拦截,所以并不需要使用toRef。如果想在模板里面直接使用{{name}}并且要具有响应性,这时才需要使用toRef。
5
浅层响应式代理: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)
注意:objectShallowReactive.contacts.QQ = 123 ,这样修改属性也是没有响应性的。
单独使用的属性的形式:
嵌套对象和name属性,都没有变成响应式。
6
做一个不允许响应的标记: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) // 可以变成响应性代理
运行结果:
7
深层只读响应式代理:readonly
// 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。
8
浅层只读响应代理:shallowReadonly
// object 的浅层只读代理
const objectShallowReadonly = shallowReadonly(person)
// reactive 的浅层只读代理
const reactiveShallowReadonly = shallowReadonly(objectReactive)
9
获取原型:toRaw
// 获取reactive、shallowReactive、readonly、shallowReadonly的原型
console.log('深层响应的原型', toRaw(objectReactive))
console.log('浅层响应的原型', toRaw(objectShallowReactive))
console.log('深层只读的原型', toRaw(objectReadonly))
console.log('浅层只读的原型', toRaw(objectShallowReadonly))
10
类型判断
isReactive:判断是否是reactive创建的代理。如果readonly的原型是reactive,那么也会返回true。
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。
11
判断依据
那么这三个函数是依据什么判断的呢?自己做的 Proxy 无意中监控到了“__v_isReactive”,难道是隐藏属性?测试了一下,果然是这样。
myProxy({title:'测试隐藏属性', __v_isReactive: true}),这样定义一个实例,也会返回true。
12
reactive直接赋值的方法
Object.assign(objectReactive, {name: '合并', age: 20, newProp: '新属性'})
13
数组的整体赋值的方法
数组就方便多了,可以先清空再 push 的方式,代码如下:
// retArray.length = 0 // 这里清空的话,容易照成闪烁,所以不要急
setTimeout(() => {
const newArray = [
{ name: '11', age: 18 },
{ name: '22', age: 18 }
]
// 等到这里再清空,就不闪烁了。
retArray.length = 0
retArray.push(...newArray)
}, 1000)
14
var 和 let、const
源码:
https://gitee.com/naturefw/nf-vue-cdn/tree/master/cdn/project-compositionapi
在线演示:
本文作者:自然框架
个人网址:jyk.cnblogs.com
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。
写的不错?赞赏一下
长按扫码赞赏我