原始值响应方案

86 阅读2分钟

ref

vue中是怎么对基本值继续代理的,Proxy无法对基本值(Boolean,String,Number等),使用vue3只用了vue2的Object.defineProperty对基本值进行代理

            // 封装ref函数
            function ref(val) {
                const wrapper = {
                    value: val,
                };

                //定义一个不可枚举属性,并且值为true
                Object.defineProperty(wrapper, '_v_isRef', {
                    value: true,
                });
                console.log(wrapper);
                return reactive(wrapper);
            }
 const refVal = ref(1);
            effect(() => {
                console.log(refVal.value);
            }); //1
            refVal.value = 2; //2

使用Object.defineProperty为wrapper定义一个不可枚举属性,值为true,代表对象是一个ref,这样就可以通过__V_isRef判断这个是不是ref. 为什么需要这个不可枚举属性

    const refVal1 = ref(1)
    const refVal2=reactive({value:1})

如果没有上面那个不可枚举属性,就没办法区分是原始值还是非原始值对象

响应丢失

ref可以解决响应丢失问题,什么是响应丢失?

    const obj = reactive({foo:1,bar:2})
    //将对象扩展
    const newObj = {...obj}
    effect(()=>{
        console.log(newObj.foo)
    )
    obj.foo =100//不会触发响应

如上面代码,修改obj.foo的值并不会触发响应,这是因为通过扩展运算符展开的是一个普通对象,没有响应能力. vue中解决方法如下 封装toRef函数

            //封装toRef函数
            function toRef(obj,key){
                const wrapper={
                    get value(){
                        return obj[key]
                    }
                }
                return wrapper
            }
            
            const newObj = {
                foo:toRef(obj,'foo'),
                bar:toRef(obj,'bar')
            }

原理其实就是重新在响应式数据中访问,把该值包装成对象,key为value返回,当然,还要考虑要转换很多属性的情况

            //封装toRefs函数
            function toRefs(obj){
                const ret = {}
                for(const key in obj){
                    ret[key] = toRef(obj,key)
                }
                return ret
            }

原理是一样的,只是for...in循环批量封装,现在我们要将转换的数据真正转为ref:为每一个数据加上不可枚举属性

            //封装toRef函数
            function toRef(obj, key) {
                const wrapper = {
                    get value() {
                        return obj[key];
                    },
                };
                Object.defineProperty(wrapper, '__v_isRef', {
                    value: true,
                });
                return wrapper;
            }

现在,ref数据可读了,还需要可以设置

            //封装toRef函数
            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;
            }

通过执行set value方法进行赋值,触发响应

脱离ref

我们封装的toRefs方法会把对象第一层封装为ref,用户使用就必须newObj.foo.value,这其实并不方便. vue中设计了自动脱离ref的功能,就是当访问属性是一个ref时,则直接将对应的value返回.要实现这个功能,vue是给newObj用proxy创建代理对象

            function proxyRefs(target){
                return new Proxy(target,{
                    get(target,key,receiver){
                        const value = Reflect.get(target,key,receiver)
                            //自动脱离ref:如果读取的值是ref则返回value
                            return value.__v_isRef ? value.value:value
                    }
                })
            }
            //调用函数创建代理
            const newObj = proxyRefs=({...toRefs(obj)})

console.log(newObj.foo)
console.log(newObj.bar)

读取属性有脱离ref功能,设置属性也应该自动为ref设置的能力

            function proxyRefs(target) {
                return new Proxy(target, {
                    get(target, key, receiver) {
                        const value = Reflect.get(target, key, receiver);
                        //自动脱离ref:如果读取的值是ref则返回value
                        return value.__v_isRef ? value.value : value;
                    },
                    set(target, key, newVal, receiver) {
                        const value = target[key];
                        //如果值是Ref,则设置对应value
                        if (value.__v_isRef) {
                            value.value = newVal;
                            return true;
                        }
                        return Reflect.set(target, key, newVal, receiver);
                    },
                });
            }

判断是不是ref,是则为它的value更改值