vue3 之 reactive && toRefs源码解析

太_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'
  2. 注意点

    ① 使用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) {}
    }
    • collectionHandlers是一个只包含get的对象。

    详细看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对象遇到解构和展开运算符之后,失去响应性的问题。

阅读 902

javascript2
已有之事将来必有,易行之事将来必行,太阳底下无新事

已有之事将来必有,易行之事将来必行,太阳底下无新事。

23 声望
0 粉丝
0 条评论
你知道吗?

已有之事将来必有,易行之事将来必行,太阳底下无新事。

23 声望
0 粉丝
宣传栏