vue3 之 reactive && toRefs源码解析

2021-03-28  本文已影响0人  太_2_真_人
一、 reactve
  1. 定义

reactive API的定义为传入一个对象返回一个基于原对象的响应式代理,即返回一个proxy,相当于Vue2.x中的Vue.Observer

  1. 优点

    在Vue2.x中数据的响应式处理是基于Object.defindProperty()的,但他只会侦听对象的属性,不会侦听对象,在添加对象属性的时候需要:

    Vue.$set(object, 'name', 'lock li')
    

    reactiveAPI则是基于ES2015 proxy实现了对对象的响应式处理,在vue3.0中往对象中添加属性,并且这个属性也会具有响应式的效果:

    object.name = 'lock li'
    
  1. 注意点

    ① 使用reactiveAPI时,setup中返回的reactive需要通过对象的形式:

    export default definComponent({
        name: 'example',
        setup(props) {
            const obj = reactive({foo: 'bar'})
            
            return {
                obj
            }
        }
    })
    

    defineComponent

    definComponent主要是用来帮助Vue在TS下正确推断出setup()组件的参数类型

    export function defineComponent(options: unknown) {
        return isFunction(options) ? { setup: options } : options
    }
    

② 或者借助toRefsAPI包裹一下导出,(使用toRefs包裹导出的我们可以使用展开运算符或解构接收):

export default defineComponent({
    setup() {
        let obj = { foo: 'bar' }
        obj = toRefs(obj)
        return {
            ...obj
        }
    }
})

二、源码实现

function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (readonlyToRaw.has(target)) {
        return target;
    }
    // target is explicitly marked as readonly by user
    if (readonlyValues.has(target)) {
        return readonly(target);
    }
    if (isRef(target)) {
        return target;
    }
    return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers);
}

可以看到先有3个判断逻辑,对readonlyToRawreadonlyValuesisRef分别进行了判断,先不看这些判断逻辑,通常我们对reactive()传入一个对象,可以直接命中createReactiveObject()

createReactiveObject()函数如下:

function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {
    if (!isObject(target)) {
        if ((process.env.NODE_ENV !== 'production')) {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // target already has corresponding Proxy
    let observed = toProxy.get(target);
    if (observed !== void 0) {
        return observed;
    }
    // target is already a Proxy
    if (toRaw.has(target)) {
        return target;
    }
    // only a whitelist of value types can be observed.
    if (!canObserve(target)) {
        return target;
    }
    const handlers = collectionTypes.has(target.constructor)
        ? collectionHandlers
        : baseHandlers;
    observed = new Proxy(target, handlers);
    toProxy.set(target, observed);
    toRaw.set(observed, target);
    return observed;
}

createReactiveObject()传入了4个参数,分别是:

target:我们定义reactive时传入的对象

toProxy:一个空的WeakSet

toRaw:判断传入的reactive是否已经被代理

baseHandlers:一个已经被定义好的的具有getset的对象,它看起来会是这样子:

const baseHandlers = {
 get(target, key, value, receiver) {},
 set(target, key, value, receiver) {},
 deleteProxy(target, key) {},
 has(target, key) {},
 ownKey(target) {}
}

详细看createReactiveObject函数,一些分支逻辑先不用管,我们直接看最后的逻辑:

const handlers = collectionTypes.has(target.constructor)
        ? collectionHandlers
        : baseHandlers;
    observed = new Proxy(target, handlers);
    toProxy.set(target, observed);
    toRaw.set(observed, target);
    return observed;

首先判断collectionTypes中是否包含我们传入的target的构造函数(构造器),而collectionTypes是一个Set集合主要包含:Set Map WeakSet WeakMap 等4种集合的构造函数。

如果collectionTypes 中包含我们传入的构造函数,则将handlers赋值为仅包含get属性的collectionHanders 否则 赋值为baseHandlers

两者的区别在于collectionHandlers仅含有get属性,是用来留给不需要派发更新的变量使用的,例如我们常用的props属性

然后将target handlers作为两个参数传入,使用替换了vue 2.x中的Object.definePropertyProxy代理函数,并实例化

接下来两步也非常重要,将targetobserved 作为键值对赋值到toProxy,用于下次检测传入的target是否已经被代理,并返回被代理的observed对象:

// target already has corresponding Proxy
let observed = toProxy.get(target);
if (observed !== void 0) {
    return observed;
}

observedtarget作为键值对赋值到toRaw,用于下次检测传入的target是否是一个代理对象,并返回代这个target

// target is already a Proxy
if (toRaw.has(target)) {
    return target;
}

二、toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。

使用toRefs其实是为了方便我们使用解构和展开运算符,具体代码:

function toRefs(object) {
    if ((process.env.NODE_ENV !== 'production') && !isReactive(object)) {
        console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = {};
    for (const key in object) {
        ret[key] = toProxyRef(object, key);
    }
    return ret;
}
function toProxyRef(object, key) {
    return {
        _isRef: true,
        get value() {
            return object[key];
        },
        set value(newVal) {
            object[key] = newVal;
        }
    };
}

可以看到toRefs是在原有Proxy的基础上,返回了一个所有属性带有getset 的对象,这样就解决了Proxy对象遇到解构和展开运算符之后,失去响应性的问题。

上一篇下一篇

猜你喜欢

热点阅读