第五节:vue3 Ref实现原理

1,313 阅读3分钟

ref的概念

proxy代理的目标必须是非原始值,所以reactive不支持原始值类型。所以我们需要将原始值类型进行包装

将原始值类型包装成对象(RefImpl类的实例就是对象)

ref不关心数据类型 数组对象基础数据类型都可以

检测到得到的是对象的话就 用reactive转成proxy

转换结果

{
    _value: false,
    get(){
        return this._value
    },
    ser(newValue){
        this._value = newValue
    }
}

Ref & ShallowRef

Ref将原始值类型包装成对象

ShallowRef创建浅ref 不会进行深层代理

使用

const {ref, effect, reactive, toRefs, proxyRefs} = VueReactivity
const flag = ref(false) // 将原始值类型包装成对象
effect(()=>{
    document.body.innerHTML = flag.value ? 'jdlyp':'lyp'
});
setTimeout(()=>{
    flag.value = true
},1000);

实现

入口函数

class RefImpl{
    constructor(public rawValue, public _shallow) {
    }
}
// 将原始类型包装成对象, 同时也可以包装对象 进行深层代理
export function ref(value) {
    return new RefImpl(value, false);
}
// 创建浅ref 不会进行深层代理
export function shallowRef(value) {
    return new RefImpl(value, true);
}

RefImpl 具体实现

  • 1、判断如果是对象 使用reactive将对象转为响应式的
  • 2、set的值不等于初始值 判断新值是否是对象 进行赋值
import { reactive } from "./reactive";
import { trackEffects, triggerEffects } from './effect'

export const isObject = (value) => {
    return  typeof value === 'object' && value !== null
}

// 将对象转化为响应式的
function toReactive(value) { 
    return isObject(value) ? reactive(value) : value
}

class RefImpl {
    public _value;
    public dep = new Set; // 依赖收集
    public __v_isRef = true; // 是ref的标识
    // rawValue 传递进来的值
    constructor(public rawValue, public _shallow) {
        // 1、判断如果是对象 使用reactive将对象转为响应式的
        // 浅ref不需要再次代理
        this._value = _shallow ? rawValue : toReactive(rawValue); 
    }
    get value(){ 
        // 取值的时候依赖收集
        trackEffects(this.dep)
        return this._value;
    }
    set value(newVal){
        if(newVal !== this.rawValue){ 
            // 2、set的值不等于初始值 判断新值是否是对象 进行赋值
            this._value = this._shallow ? newVal : toReactive(newVal);
            // 赋值完 将初始值变为本次的
            this.rawValue = newVal 
            triggerEffects(this.dep)
        }
    }
}

toRef&toRefs 解决解构时响应式丢失问题

toRefs将所有的属性转换成ref (再包装了一层对象)

toRefs参数必须是响应式的对象

const company = reactive({name:'jd',age:'20'})
// let {name,age} = company // 会丧失响应式
let {name,age} = toRefs(company)
effect(()=>{
    document.body.innerHTML = `${name.value}今年${age.value}岁了`
});
setTimeout(()=>{
    age.value = 31;
},1000)

实现

 // 只是将  响应式对象的.value属性  代理到原始类型上
class ObjectRefImpl {
    public __v_isRef = true
    constructor(public _object, public _key) { }
    get value() {
        return this._object[this._key];
    }
    set value(newVal) {
        this._object[this._key] = newVal;
    }
}

// 将响应式对象中的某个属性转化成ref
export function toRef(object, key) { 
    return new ObjectRefImpl(object, key);
}

// 循环 将所有的属性转换成ref 要识别是对象还是数组
export function toRefs(object){ 
    const ret = Array.isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key);
    }
    return ret;
}

自动脱ref(proxyRefs方法)

不使用.value 就能取到值 反向取ref 一般在模板中使用

后续我们取值都是在模板中取值的

在使用数据的时候 都是把响应式的数据 toRefs,在使用的时候不需要+.value

let name = ref('lyp')
let age = ref('20')
let person = proxyRefs({name,age})
effect(()=>{
    document.body.innerHTML = `${person.name}今年${person.age}岁了`
})
setTimeout(()=>{
    person.age = 31;
},1000)

实现

export function proxyRefs(object){
    return new Proxy(object,{
        get(target,key,receiver){
            let v = Reflect.get(target,key,receiver);
            // 看一下是不是ref 是的话自动.value 不是的话返回原值
            return v.__v_isRef? v.value:v; 
        },
        set(target,key,value,receiver){ 
            const oldValue = target[key];
            // 看一下是不是ref 是的话自动给.value赋值 不是的话直接用Reflect去设置
            if(oldValue.__v_isRef){ 
                oldValue.value = value;
                return true
            }else{
                return Reflect.set(target,key,value,receiver)
            }
        }
    })
}

不支持脱ref的情况

数组中的ref是不支持脱ref的,访问的时候必须加上.value

使用数组 + 下标的方式访问ref的时候(proxyArr[0])不进行脱ref

const proxyArr = reactive([ref(1),2,3])
console.log(proxyArr[0].value) // 这种情况不支持脱ref 必须加上.value