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

1,475 阅读5分钟

上一篇我们探索了简单数据类型ref之后的结果,还记得整个过程吗?我们生成了一个RefImpl对象,对象上面挂载了__v_isRef、_rawValue、_shallow、_value四个属性,且_rawValue和_value的值相等。当我们触发get获取操作时,返回_value的值,触发set更新操作时,同时更新_rawValue和value的值(两个值相等)。

这篇我们要了解的是ref对复杂类型数据是如何处理的。

//输入
const {ref} = VueReactivity;
const counter = ref({
    a:{
        b:{
            c:123
        }
    }
});

传进去一个深层嵌套的对象,看看发生了什么。

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

还是new RefImpl。

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

可以看到_rawValue仍然储存的是我们传进去的值,但是_value会有所变化。

我们看看convert这次会有什么不一样。

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

可以发现,对于复杂数据类型,会执行reactive函数,把它变成响应式的,这与之前的简单数据类型处理已经不同,看看reactive函数都做了什么。

function reactive(target) {    
    // if trying to observe a readonly proxy, return the readonly version.    
    if (target && target['__v_isReadonly' /* IS_READONLY */]) {      
        return target;    
    }    
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);  
}

第一个判断语句判断是否有__v_isReadonly,显然不会执行,看看后面的createReactiveObject函数,从名字看,创建响应式对象,wooo~~~,未知的神秘都在里面,看看都有什么。

function createReactiveObject(  
    target, isReadonly, baseHandlers, collectionHandlers ) 
{    
    ...
    ...    
    // target already has corresponding Proxy    
    const proxyMap = isReadonly ? readonlyMap : reactiveMap;    
    const existingProxy = proxyMap.get(target);    
    if (existingProxy) {      
        return existingProxy;    
    }    
    // only a whitelist of 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;  
}

前两个判断语句不会执行,所以我这里省略。先打印看下传递的四个值分别是什么。

前两个很好理解,一个是我们传递进去的值,一个是默认给的false,但是后面两个就有点一头雾水了。what???发生了什么???mutableHandlers和mutableCollectionHandlers对象里面都包裹着函数,但是不要紧,这些函数现在还没有调用,都不会执行,所以我们之后再慢慢解决,先看createReactiveObject代码一一执行发生了什么。

//isReadonly默认为false,所以proxyMap = reactiveMap
const proxyMap = isReadonly ? readonlyMap : reactiveMap;

const reactiveMap = new WeakMap();
const readonlyMap = new WeakMap();

可以看出来proxyMap现在是一个空的WeakMap集合。

const existingProxy = proxyMap.get(target);
if (existingProxy) {    
    return existingProxy;
}

但是reactiveMap和readonlyMap里面存放的都是什么?上面的代码可以推测出存放的就是我们ref时传进去的对象(对象作为键,而对应的值就是proxy),可以想象一下,如果有多个不同的对象被ref,reactiveMap和readonlyMap里面都会存放多个对应的对象和对象的proxy。

const targetType = getTargetType(target);

看一下getTargetType函数。

function getTargetType(value) {    
    return value['__v_skip' /* SKIP */] || !Object.isExtensible(value) 
     ? 0 /* INVALID */   : targetTypeMap(toRawType(value));
}

因为我们输入的值是一个深层嵌套的对象,里面没有__v_skip属性并且是可扩展的,所以返回的值是targetTypeMap(toRawType(value))。

先看下toRawType函数的作用。

const toRawType = value => {    
    // extract "RawType" from strings like "[object RawType]"    
    return toTypeString(value).slice(8, -1);
}

const objectToString = Object.prototype.toString;
const toTypeString = value => objectToString.call(value);

可以看出toRawType(value) === Object.prototype.toString.call(value).slice(8, -1),为了判断对象是哪种类别,这里因为我们传入的是Object,所以toRawType(value)的结果为object,再看看targetTypeMap函数。

function targetTypeMap(rawType) {    
    switch (rawType) {      
        case 'Object': ;      
        case 'Array':        
            return 1 /* COMMON */;      
        case 'Map': ;     
        case 'Set': ;     
        case 'WeakMap': ;     
        case 'WeakSet': ;       
        return 2 /* COLLECTION */;      
        default:        
            return 0 /* INVALID */;    }  }

可以看出来函数的作用是Object和Array返回1,Map、Set、WeakMap、WeakSet返回2,否则都是0。所以结果是1。

if (targetType === 0 /* INVALID */) {      return target;    }

从接下来的判断语句可以推测出ref对复杂类型的处理只接受Object、Array、Map、Set、WeakMap、WeakSet这几种类型。

const proxy = new Proxy( target,  
    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers;    );

看到这里,我们终于接触到了proxy。来看看都干了些啥。

可以看出来Proxy接受两个参数,一个是target,就是我们传入的值,现在我们传入的是一个深层嵌套的对象。第二个参数可以发现它是动态的,如果是Object和Array类型将传入baseHandlers,否则传入collectionHandlers。

如之前讲到的,baseHandlers和collectionHandlers中有一些函数,但是它们现在没有被调用,所以我们不用关心太多,只需要知道此时已经生成了一个proxy,更多的细节在之后再去研究。

proxyMap.set(target, proxy);
return proxy;

最后两行代码,将target和proxy键值对放到readonlyMap或者reactiveMap中,然后将proxy返回。

我们研究了这么久createReactiveObject函数,还记得是在哪里调用的吗?在reactive函数内,最终会返回给this._value,也就是RefImpl对象的_value。看下打印结果。

可以发现,和简单数据类型对比,复杂类型的ref其实只有一个地方有所不同,就是_value,_value的值就是一个Proxy。

让我们梳理一下整个过程:

我们先将一个深层嵌套的对象传递进去,会通过reactive函数来将传递进去的值变成可响应式的,而要做的就是先判断传递进去值是什么类型,根据不同的类型来使用不同的handlers(要么是collectionHandlers要么是baseHandlers),最终根据value,以及不同的handlers,来生成一个proxy。然后以键值对的方式将value和proxy存储到readonlyMap或者reactiveMap中(它们是一个WeakMap集合),最后,将proxy赋值给this._value,也就是RefImpl的_value。

比较简单数据类型和复杂数据类型的ref有什么不同:

简单数据类型的_value和_rawValue相同,复杂数据类型的_rawValue是我们输入的值,_value是根据输入的值和Handlers生成的proxy,在过程中还会以键值对的方式将value和proxy推入到一个WeakMap集合中存储。