第六章主要讲解的是原始值的响应式方案,reactive,ref,toRef,toRefs之间的关系,以及他们的作用,简单来说,ref是由ractive构建的,toRefs是由toRef构建的
6.1 引入ref的概念
1.原始值是指 Nubmer,Boolean,BigInt,String,Symbol,undefined,null,即非对象类型
2.proxy是无法代理原始值的,只能代理对象(函数也是对象)
3.要区分原始值和非原始值的响应
如何解决上面的问题,其实就是将原始值包裹一层非原始值即可,然后用reactive来创建非原生值响应,那么如何确定非原生值和原始值响应呢?我们给创建的非原始值,设置一个不可遍历的属性即可,打个标记’__v_isRef,如下代码
function ref(val) {
const wrapper = {
value: val
}
Object.defineProperty(wrapper, '__v_isRef', {
enumerable:false,
value: true
})
return reactive(wrapper)
}
function isRef(val) {
return val.__v_isRef === true
}
const refVal = ref(1)
console.log(isRef(refVal))
effect(() => {
console.log(refVal.value)
})
refVal.value = 2
那么ref可以使用非原生值吗?
由源码看是可以的,ref默认使用 深响应,可读属性,如果非原生值,相当于
reactive({ value: obj, __v_isRef:true}) 这种形式
6.2 响应丢失问题
1.响应丢失主要是说,用…操作符,扩展响应数据的结果会丢失响应,
const obj= reactive({foo:1,bar:2})
const newobj={ ...obj} // 相当于 const newobj={ foo:1,bar:2} 变成普通变量
解决方法就是将newobj的访问的值用 obj的替换,代码如下:
const obj = reactive({ foo: 1, bar: 2 })
// const newObj = {
// ...obj
// }
// effect(() => {
// console.log(newObj.foo)
// })
// obj.foo = 100
// 将响应是对象全部key,全部提前到对象中
function toRefs(obj) {
const ret = {}
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
// 将响应式对象的某个key,提取出来变成响应式
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key]
},
// 扩展出来的响应值也可以修改
set value(val) {
obj[key] = val
}
}
// 把obj的key都转成ref,不管他是不是原始值,所以打上标记
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
const refFoo = toRef(obj, 'foo')
const refBar = toRef(obj, 'bar')
console.log(refFoo.value)
console.log(refBar.value)
const newObj = { ...toRefs(obj) }
console.log(newObj.foo.value)
console.log(newObj.bar.value)
refFoo.value = 100
console.log(refFoo.value)
toRefs() 参数是一个ractive响应对象,返回的式一个{ 响应式对象key的响应对象},也就是说toRefs本身式普通对象,但是他的key全部都是响应式对象
toRef(obj,key) 表示分离出obj的key,为响应式对象
注意这返回的结果,的响应式对象都需要用xx.value访问,因为不知道 obj的是不是原生值,都转成ref的形式,
那么toRefs和toRef的第一个参数可以是ref吗?
由源码可以知道,toRefs会遍历obj,而如果是ref,结构是reactive({ value: obj, __v_isRef:true}) 导致ret的结构是{ value: toRef(obj,value )}
如果
const str=ref("abcd") const strs=toRefs(str)
那么要访问到abcd 则是:strs.value.value
但是vue3源码如下
function toRefs(object) {
if (!isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`);
}
const ret = isArray(object) ? new Array(object.length) : {};
for (const key in object) {
ret[key] = toRef(object, key);
}
return ret;
}
function isProxy(value) {
return isReactive(value) || isReadonly(value);
}
toRaw的实现原理
这里提一下toRaw这个方法,他是返回响应式对象的原始值,其实之前的代码中就有,当访问响应式对象的raw就返回target ,而vue3源码如下,
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
if (!shared.isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// only specific value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
function toRaw(observed) {
const raw = observed && observed["__v_raw" /* RAW */];
return raw ? toRaw(raw) : observed;
}
当访问__v_raw的时候就返回原生对象target
6.3 自动脱ref
这个是要解决,上面toRefs把reactive的属性都变成ref,ref都需要用.value访问,我们要改成ref对象能直接访问属性,让newObj.foo.value 不用.value就能直接访问到最终的可代理对象,实现如下:
const obj = reactive({ foo: 1, bar: 2 })
// const newObj = {
// ...obj
// }
// effect(() => {
// console.log(newObj.foo)
// })
// obj.foo = 100
function toRefs(obj) {
const ret = {}
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key]
},
set value(val) {
obj[key] = val
}
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
function proxyRefs(target) {
return new Proxy(target, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
return value.__v_isRef ? value.value : value
},
set(target, key, newValue, receiver) {
const value = target[key]
if (value.__v_isRef) {
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
const newObj = proxyRefs({ ...toRefs(obj) })
console.log(newObj.foo)
console.log(newObj.bar)
newObj.foo = 100
console.log(newObj.foo)
proxyRefs的参数就是{…toRefs(obj) } ,注意不是ref对象,是{ ref }
如果 参数对象的属性是ref,就直接返回他的value,如果不是就返回属性本身
设置的时候,如果属性是ref,将值赋值给他的value,否则直接返回
vue的setup返回的值都会自动脱钩,执行proxyRefs 所以在模板中不用使用.vule
returen { newObj.foo }
ref中可以嵌套ref吗?proxyRefs还可以脱ref吗?
ref中嵌套ref是没有效果的,在vue3中会判断ref传入的值是不是ref
function ref(value) {
return createRef(value, false);
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
问题总结:
第六章 原始值的响应式方案
(二十二)如何实现原始值的响应式?响应丢失是什么情况,如何解决?为什么要自动脱ref?
(1)可以利用非原始值响应式方案,用一个对象包裹原始值,然后给对象上定义一个_v_isRef表示不可枚举属性
function ref(val) {
const wrapper = {
value: val
}
Object.defineProperty(wrapper, '__v_isRef', {
enumerable:false,
value: true
})
return reactive(wrapper)
}
function isRef(val) {
return val.__v_isRef === true
}
(2)响应丢失是指如果使用展开运算符…在 reactive对象,结果会失去响应式,会返回普通对象值,可以封装一toRef他可以为…的结果创建一个新响应式的值,toRefs可以批量将响应对象扩展的值全部变成ref形式,这样使用…的结果就不会丢失响应式了
(3)因为在模板中访问数据,如何还是xx.value的话,给用户增加心智,所以所有的setup中的ref都会自动脱ref
本文深入探讨Vue3中原始值的响应式方案,包括ref、reactive等核心概念的实现原理,以及toRefs和toRef的功能与应用场景,最后介绍了自动脱ref的实现方式。
1005

被折叠的 条评论
为什么被折叠?



