您现在的位置是:亿华云 > 域名

Vue.js设计与实现之十-原始类型的响应式代理

亿华云2025-10-09 03:56:05【域名】7人已围观

简介1、写在前面在javascript中原始值包括:Boolean、String、Number、Null、Undefined、Symbol和BigInt等类型,原始值是按值传递而非按引用传递。前面,知道P

1、计实写在前面

在javascript中原始值包括:Boolean、原始应式String、类型Number、响代理Null、计实Undefined、原始应式Symbol和BigInt等类型,类型原始值是响代理按值传递而非按引用传递。前面,计实知道Proxy可以用于实现对象类型的原始应式响应式代理,但是类型却不能实现原始值的代理,要实现原始值变成响应式数据,响代理就需要做些处理。计实

2、原始应式ref

Proxy的类型代理目标必须是对象类型,那么是否可以将原始值类型包装成对象类型,这样不就可以实现代理了吗?

// let name = "pingping"

const data = {

value: "pingping"

}

const state = reactive(data);

name.value = "onechuan";

想法是很好,但是你想过没有这样做带来的问题:

用户创建一个原始值的响应式数据,就必须创建一个包裹的对象。而包裹对象又是由用户自定义,那么就存在命名和使用不规范情况。源码库

解决方法很简单,你不是担心用户自定义的对象不规范不可控吗,那么就在源码内部定义不就行了。

function ref(val){

const wrapper = {

value: val

}

return reactive(wrapper);

}

简单试用下:

const refVal = ref("pingping");

effect(()=>{

console.log(refVal.value);

});

refVal.value = "onechuan";

但是,在使用过程中又有个问题:你又是如何保证refVal是原始值的包裹对象,还是一个非原始值的响应式数据呢?

const refVal = ref("pingping");

const refVal2 = reactive({ value:"pingping"});

其实,ref和reactive生成的响应式数据实现方式都是一样的,对数据来源区分是不是ref是为了后续脱ref,脱出响应式能力恢复原始数据。

function ref(val){

const wrapper = {

value: val

}

Object.defineProperty(wrapper,"__v_isRef",{

value: true

})

return reactive(wrapper);

}

在上面代码中,使用Object.defineProperty给包裹对象wrapper定义一个不可枚举和不可写的属性"__v_isRef",使其值为true用于区分当前对象是ref而非普通对象。

简而言之:ref其实是对一个对象和reactive的二次封装。

3、响应丢失的云服务器提供商问题

我们知道,ref可以用于实现原始值的响应式代理,但其实还可以用于解决响应式丢失的问题。所谓响应式丢失,就是在使用reactive生成的响应式对象数据,使用展开运算符(...)会丢失响应式,就成了一个普通对象数据。此时,修改修改对象的属性值,不会触发更新和模板渲染。

const obj = reactive({ name:"pingping"});

const newObj = { ...obj};

effect(()=>{

console.log(newObj.name);

});

obj.nmae = "onechuan";

在上面代码中,副作用函数中访问的只是普通对象newObj的属性name的值,它并不具有响应式能力,在对其属性值进行修改时,不会触发副作用函数重新执行。

那么,应该如何解决响应式丢失的问题呢?

其实就是能解决在副作用函数中,通过获取普通对象newObj的属性值,也会触发更新,与副作用函数建立联系。

通过在普通对象newObj中设置与obj对象同名的亿华云计算属性,将每个属性值都设置成对象,通过对象的get取值方法实现obj对象的属性值读取,这样就巧妙地将newObj的属性值与副作用函数建立了联系。

const obj = reactive({ name:"pingping"});

const newObj = { ...obj};

effect(()=>{

console.log(newObj.name);

});

obj.nmae = "onechuan";

但是,如果obj对象中有很多属性,那是不是就需要在newObj建立许多同名的对象?那么,就可以进行抽取封装函数:

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;

}

在使用过程中,简简单单:

const obj = reactive({ name:"pingping"});

const name = toRef(obj, "name");

name.value = "onechuan";

前面只是对少数对象的属性值转成响应式数据可以这样处理,但是当我们需要批量处理数据,应该如何处理呢?

很简单,对对象属性进行遍历不就得了。

function toRefs(obj){

const res = { };

for(const key in obj){

res[key] = toRef(obj,key);

}

return res;

}

这样,响应式丢失问题就被解决了,方法就是将响应式数据转换成类似ref结构的数据,通过toRef或toRefs转换后得到的数据就是真正的ref数据。

4、自动脱ref

使用toRefs用于解决响应丢失问题,就是对对象的属性进行遍历转为ref,这样就会带来新问题,就是去访问数据的第一层属性,必须通过.value才能访问。这样无疑会增加使用者的心智负担,用户肯定愿意直接对象.属性,而非通过对象.属性.value来使用属性值。

const obj = reactive({

name:"pingping",

age:18

});

const newObj = {

...toRefs(obj)

};

newObj.name.value//pingping

newObj.age.value//18

现在我们就需要让其自动脱ref,这样在进行对象属性的访问时,读取到属性是个ref则放回ref.value,否则直接返回属性值。

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;

}

})

}

const newObj = proxyRefs(...toRefs(obj));

在上面代码中,通过定义一个proxyRefs函数接收一个对象参数,返回该对象的代理对象。而代理对象的作用是通过get操作,在读取到对象的属性是个ref值时,直接返回该ref.value值,否则直接返回属性值,这样就实现了自动脱ref。

其实,在模板中使用ref的属性值时,就是通过将组件setup返回的数据传递到proxyRefs函数中进行处理。这样就可以实现,在模板中直接访问属性值,而非属性.value值。

前面有实现自动脱ref的能力,现在就有实现自动穿ref的能力。实现原理,同样的是通过添加对应的set拦截函数。

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);

}

})

}5、写在最后

在本文中主要介绍了如何将原始值转为响应式数据,如何解决响应式丢失的问题,如何减少用户心智负担实现自动脱ref的能力等。ref本质就是一个包裹对象,通过reactive实现对原始值的响应式代理,但是包裹对象自爱本质上又和普通对象没啥区别,对此需要通过设置一个标识符__v_isRef来实现ref数据的区分。

很赞哦!(2)