探索Vue3响应式API之Ref(一)

2,362 阅读3分钟
//输入
const {ref} = VueReactivity;
const counter = ref(0);
console.log(counter.value);
counter.value = 1;

第一个函数:

function ref(value) {   
 return createRef(value);
}

可以看到返回的是调用createRef(0)之后的结果,接下来看看createRef函数。

function createRef(rawValue, shallow = false) {    
    if (isRef(rawValue)) {     
         return rawValue;   
     }    
    return new RefImpl(rawValue, shallow); 
}

这里shallow默认为false,整个函数的作用是判断rawValue是不是Ref,如果是,直接返回,否则创建新的Ref。

如何判断传进去的值是不是已经是ref,函数如下:

 function isRef(r) {    
    return Boolean(r && r.__v_isRef === true);
 }

可以看出来是通过传进去的值的__v_isRef属性,因为之前输入传的是0,0不是对象,显然返回结果为不是Ref。

接下来看看new RefImpl发生了什么。

class RefImpl {    
    constructor(_rawValue, _shallow = false) {      
        this._rawValue = _rawValue;     
        this._shallow = _shallow;     
        this.__v_isRef = true;      
        this._value = _shallow ? _rawValue : convert(_rawValue);    
    }   
     get value() {      ...    }   
     set value(newVal) {      ...    } 
 }

RefImpl是一个类,当实例化的时候自动调用constructor内的代码,其他都好理解,这里看下convert函数干了什么。

  const convert = val => (isObject(val) ? reactive(val) : val)

可以看出该函数作用是判断我们输入的值(0)是不是一个对象,是的话将对象变为响应式的,否则不做改动。

打印看下new Reflmpl之后最终结果。

和预期的一致,接下来看下get value里面作了什么(因为之前输入打印了counter.value,所以get value里面的代码都会执行)。

get value() {    
    track(toRaw(this), 'get' /* GET */, 'value');  
    return this._value;
}

先看toRaw。

function toRaw(observed) {    
    return (observed && toRaw(observed['__v_raw' /* RAW */])) || observed;
}

这里用到了递归,但是因为我们传入的是普通类型0,所以并不复杂,结果没有做任何变化,返回this,再看看track。

let shouldTrack = true;
...
let activeEffect;
...
function track(target, type, key) {    
    if (!shouldTrack || activeEffect === undefined) {      
        return;  
    }    
    ...
 }

这里第一个判断语句直接返回,从这里就可以看出ref对普通类型做的处理很少。

梳理一下整个过程,调用ref传0进去,会生成一个Reflmpl对象,对象内部分别挂在了__v_isRef: true,_rawValue: 1,_shallow: false,_value: 1。_rawValue和_value的值相等。

使用对象.value时返回对象._value,其他什么也不做。很简单,对吧。

接下来看看给.value赋值的时候会发生什么(之前counter.value = 1会触发set内的代码)。

set value(newVal) {      
    if (hasChanged(toRaw(newVal), this._rawValue)) {        
        this._rawValue = newVal;        
        this._value = this._shallow ? newVal : convert(newVal);        
        trigger(toRaw(this), 'set' /* SET */, 'value', newVal);    
     }  
 }

分别看下toRaw和hasChanged函数。

function toRaw(observed) {    
    return (observed && toRaw(observed['__v_raw' /* RAW */])) || observed;
}

const hasChanged = (value, oldValue) => {    
    return value !== oldValue && (value === value || oldValue === oldValue);  
}

可以看出toRaw对简单类型1没有做任何改动,hasChanged比较新值1和旧值0返回true,表示值是被改变了。

更新_rawValue和_value的值(对于简单类型直接是新值)。接下来看最后的trigger发生了什么。

const targetMap = new WeakMap();
...
function trigger(target, type, key, newValue, oldValue, oldTarget) {    
    const depsMap = targetMap.get(target);
    if (!depsMap) {      
        // never been tracked      
        return;    
    }    
    ...  
}

trigger函数又臭又长,看起来很复杂,但实际上用到部分不多(再次感叹对简单类型的处理)。这里因为targetMap里面没有数据直接return。

总结一下,对于简单数据类型的处理,ref做的全部工作。实例化一个RefImpl对象,分别挂载__v_isRef: true,_rawValue: 变量,_shallow: false,_value: 变量 几个属性,_rawValue和_value相等。使用对象的value时,触发get,不做任何处理直接返回_value。给对象的value重新赋值时,_rawValue和_value的值同时更新(同样两个值相同)。

简化一下整个过程:一个对象,使用对象的value,重新赋值对象的value。

结语:

好像又学到了点什么。