Vue3源码 第二篇-Reactive API

Vue3源码笔记系列文章

Vue3源码 第一篇-总览
Vue3源码 第三篇-Vue3是如何实现响应性
Vue3源码 第四篇-Vue3 setup
Vue3源码 第五篇-Vue3 模版compile AST生成篇


前言

Vue3中Reactive API作为基础,读懂他的源码是非常重要的,因为无论是Option Api还是Composition Api,都依赖于Reactive API 提供响应式的支持,在我们后面学习Option API和Composition API有着很大的帮助,只有搞清楚了Reactive API才能更好地进行后面的学习,学习过程中需要有一些前置的知识MDN官方proxy语法,大家需要学习下ES6的Proxy。

一、Reactive API是什么?

Vue3 Reactive API章节
Vue2中是通过数据劫持Object.defineProperty来实现数据的响应式的,而3版本中对响应式做了重构,采用了Proxy数据代理的方法来实现整个Reactive API的底层实现,一会会通过例子带大家理解Reactive API的每一个方法,并且附带上对应的源码分析!话不多说,冲冲冲~在这里插入图片描述

二、Reactive API 具体使用

1.reactive

返回对象的响应式副本,代码如下(示例):

const {reactive} = Vue;
const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})

源码

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, reactiveMap);
}

我们先了解下createReactiveObject方法的5个参数:
1.target就是我们传入的对象
2.false是否是只读,这里并不是只读,所以传入false
3.mutableHandlers 作为proxy函数的handler

const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
};

4.mutableCollectionHandlers 同作为proxy函数的handler,根据target类型的不同来决定绑定mutableHandlers还是mutableCollectionHandlers

const mutableCollectionHandlers = {
   get: createInstrumentationGetter(false, false)
};

5.reactiveMap 是一个WeakMap的Cache,为什么选择WeakMap不是Map,本人觉得是key为对象时GC无法回收Map中的值,作为Cache来说WeakMap更好。
介绍完了参数在来看一下createReactiveObject函数

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
   // 如果不是对象类型就返回不处理
   if (!isObject(target)) {
        {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // target is already a Proxy, return it. 如果已经是走过内部的proxy了就不处理
    // exception: calling readonly() on a reactive object
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
    // target already has corresponding Proxy 查看缓存里是否已经存在了该对象
    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;
    }
    // 对target进行proxy代理
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    // 放入到缓存中
    proxyMap.set(target, proxy);
    return proxy;
}

该函数主要作用就是将给定的target进行proxy代理,当然对于不同的target有不一样的handler,通过targetType来区别不同的target,这里简单把代码贴上来,并不是很难理解,对于Object和Array我们在proxy时使用的是mutableHandlers而Map,Set等类型使用的是mutableCollectionHandlers,这2个handler会在之后的模版函数到真实Dom渲染的章节和响应式更新章节来具体讲解其中的源码,在这里只是了解Reactive API的源码。

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 */;
        }
    }

2.readonly

返回一个只读的Proxy代码如下(示例):

const {reactive, readonly} = Vue;

const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})

const copyPet = readonly(pet);

源码

function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}

可以看出和reactive非常相似,第二参数传入readonly为true,第三第四个Proxy的代理handler变为readonly的处理函数,Cache也改为单独的readonly的Cache,我们可以看一下readonlyHandlers函数,set和deleteProperty都无法操作。

const readonlyHandlers = {
    get: readonlyGet,
    set(target, key) {
        {
            console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
        }
        return true;
    },
    deleteProperty(target, key) {
        {
            console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target);
        }
        return true;
    }
};

3.isProxy,isReactive,isReadonly

检查对象是否是由 reactive 或 readonly 创建的 proxy 代码如下(示例):

const {reactive, readonly, isProxy} = Vue;

const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})

const copyPet = readonly(pet);

isProxy(pet);
isProxy(copyPet);

源码

function isProxy(value) {
    return isReactive(value) || isReadonly(value);
}
function isReactive(value) {
    if (isReadonly(value)) {
        return isReactive(value["__v_raw" /* RAW */]);
    }
    return !!(value && value["__v_isReactive" /* IS_REACTIVE */]);
}
function isReadonly(value) {
    return !!(value && value["__v_isReadonly" /* IS_READONLY */]);
}

判断逻辑是通过传入的值是否存在内部属性__v_raw,__v_isReactive,__v_isReadonly来判断。为什么这么判断呢,是因为经过readonly和reactive函数的对象返回的是对象的proxy,在proxy访问上面3个属性时存在特殊处理,我们可以简单看一下proxy的get相关部分代码

function createGetter(isReadonly = false, shallow = false) {
        return function get(target, key, receiver) {
            if (key === "__v_isReactive" /* IS_REACTIVE */) {
                return !isReadonly;
            }
            else if (key === "__v_isReadonly" /* IS_READONLY */) {
                return isReadonly;
            }
            else if (key === "__v_raw" /* RAW */ &&
                receiver ===
                (isReadonly
                    ? shallow
                        ? shallowReadonlyMap
                        : readonlyMap
                    : shallow
                        ? shallowReactiveMap
                        : reactiveMap).get(target)) {
                return target;
            }
            const targetIsArray = isArray(target);
           // 其他逻辑处理
        };
    }

当然这种判断是可以判断是否是reactive和readonly函数创建的,但不一定正确(杠精一下),因为通过源码其实我们是知道判断逻辑的,我们可以写自己的proxy的handler来处理这些逻辑使得返回结果一样。当然不会有人这么闲~~

4.toRaw

返回 reactive 或 readonly 代理的原始对象 代码如下(示例):

const {reactive,toRaw} = Vue;

const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})

console.log(toRaw(pet) === pet) // true

源码

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

逻辑并不难,就是判断是否存在该对象,从之前介绍的proxy get中我们也能看到对__v_raw的处理,就是从不同的Cache中去取对应的target,这里如果不是reactive和readonly创建出来的就直接返回原值。

5.markRaw

标记一个对象,使其永远不会转换为 proxy。返回对象本身。 代码如下(示例):

const {reactive, markRaw, isReactive} = Vue;

const petRaw = markRaw({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})

console.log(isReactive(reactive(petRaw))) // false

源码

function markRaw(value) {
    def(value, "__v_skip" /* SKIP */, true);
    return value;
}
const def = (obj, key, value) => {
    Object.defineProperty(obj, key, {
        configurable: true,
        enumerable: false,
        value
    });
};

本质上是通过给对象加上__v_skip属性来避免被处理,Vue3中的Vnode也有该属性,都是为了避免被createReactiveObject创建proxy。在createReactiveObject函数中没有单独介绍的一个方法getTargetType,下面是该函数的源码

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

也就是当我们的对象通过readonly或者reactive创建时如果target上存在__v_skip属性,将不会处理。

6.shallowReactive,shallowReadonly

创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。。暴露原始值是因为没有proxy,拿到的就是原始值 代码如下(示例):

const {shallowReactive, isReactive} = Vue;

const pet = shallowReactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity",
    dashboard: {
        speed: "A",
        Growth: "S"
    }
})
console.log(isReactive(pet.dashboard)); // false

源码

function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, 
    shallowCollectionHandlers, shallowReactiveMap);
}

跟reactive的区别在于proxy的handler中的处理逻辑不同,代码如下

function createGetter(isReadonly = false, shallow = false) {
        return function get(target, key, receiver) {
            // ......其他逻辑
            const res = Reflect.get(target, key, receiver);
            // ......其他逻辑
            if (shallow) {
                return res;
            }
            if (isRef(res)) {
                // ref unwrapping - does not apply for Array + integer key.
                const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
                return shouldUnwrap ? res.value : res;
            }
            if (isObject(res)) {
                // Convert returned value into a proxy as well. we do the isObject check
                // here to avoid invalid value warning. Also need to lazy access readonly
                // and reactive here to avoid circular dependency.
                return isReadonly ? readonly(res) : reactive(res);
            }
            return res;
        };
    }

shallowReactive传入的shallow为true,可以看到当得到值之后如果是shallow就直接返回,不再去为子对象创建proxy了。shallowReadonly其实原理也是这样的,没有太大的差别,就不单独介绍了!

7.ref

ref作用和reactive差不太多,但是简单来说ref作用在基础类型,并非对象 这里用官网的示例 代码如下(示例):

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

源码

function ref(value) {
    return createRef(value);
}
function createRef(rawValue, shallow = false) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}
class RefImpl {
    constructor(_rawValue, _shallow = false) {
        this._rawValue = _rawValue;
        this._shallow = _shallow;
        this.__v_isRef = true;
        this._value = _shallow ? _rawValue : convert(_rawValue);
    }
    get value() {
        track(toRaw(this), "get" /* GET */, 'value');
        return this._value;
    }
    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);
        }
    }
}
function isRef(r) {
    return Boolean(r && r.__v_isRef === true);
}
const convert = (val) => isObject(val) ? reactive(val) : val;

通过源码我们看出来为什么通过.value去获取值,因为构建了RefImpl类,类中对value的get,set方法进行了创建,所有我们对ref操作都是对value的操作,我们还能发现一点,当我们传入的是对象时,他会帮我们调用reactive方法。

8.unref

如果参数是一个 ref,则返回内部值,否则返回参数本身
源码

function unref(ref) {
    return isRef(ref) ? ref.value : ref;
}

这个很简单的代码,就是一个简单的判断。

9.toRef

将从响应式对象上的某个属性创建ref,保留对其源的响应式连接,示例:

const {reactive,toRef} = Vue;

const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
})
const name = toRef(pet, 'name');
pet.name = "杰尼龟";
console.log(name.value); // 杰尼龟

源码

function toRef(object, key) {
    return isRef(object[key])
        ? object[key]
        : new ObjectRefImpl(object, key);
}
class ObjectRefImpl {
   constructor(_object, _key) {
        this._object = _object;
        this._key = _key;
        this.__v_isRef = true;
    }
    get value() {
        return this._object[this._key];
    }
    set value(newVal) {
        this._object[this._key] = newVal;
    }
}

源码并不难,可以看到这里如果传入的值不是响应式的就会通过ObjectRefImpl类创建一个拥有对value访问的get和set方法,get和set方法中是对源对象的引用进行访问,并非赋值操作,如果传入的Object是响应式的就直接返回。

10.toRefs

官方解释:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。 其实就是对对象中的每个属性进行toRef 代码如下(示例):

const {reactive, toRefs} = Vue;
const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity"
});
const name = toRefs(pet);

源码

function toRefs(object) {
   if (!isProxy(object)) {
        console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key);
    }
    return ret;
}

从源码中我们可以知道是可以传入普通对象的,普通对象会在toRef中进行转换,之前已经在toRef中提到了,简单来说就是对传入对象的每一个属性进行toRef操作。

11.isRef

检查值是否为一个 ref 对象。 代码如下(示例):

const {reactive, toRef, isRef} = Vue;

const pet = reactive({
    name: "皮卡丘",
    voice: "pka~pka",
    power: "electricity",
})
const name = toRef(pet, 'name');
console.log(isRef(name));// true

源码

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

源码很简单就是对比传入对象里是否包含__v_isRef 属性,我们之前介绍的源码中ref和toRef创建的过程中涉及到RefImpl和ObjectRefImpl的构造函数中都包含一行代码**this.__v_isRef = true;**包括后面要介绍的customRef,构造函数中都有这行代码。

12.customRef

官网解释:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。 代码如下(示例):一个简单的异步更改名字的customRef

<section>
    早啊!打工人{{ name }}
</section>
const {customRef} = Vue;
const Counter = {
    setup() {
        const name = nameAsyncRef("皮卡丘");
        return {
            name
        }
    }
}

function nameAsyncRef(value) {
    return customRef((track, trigger) => {
        setTimeout(() => {
            value = "杰尼龟";
            trigger();
        },2000);
        return {
            get() {
                track();
                return value
            },
            set(newValue) {
                value = newValue;
                trigger();
            }
        }
    })
}

源码

function customRef(factory) {
    return new CustomRefImpl(factory);
}
class CustomRefImpl {
    constructor(factory) {
        this.__v_isRef = true;
        const { get, set } = factory(() => track(this, "get" /* GET */, 'value'), () => trigger(this, "set" /* SET */, 'value'));
        this._get = get;
        this._set = set;
    }
    get value() {
        return this._get();
    }
    set value(newVal) {
        this._set(newVal);
    }
}
function track(target, type, key) {
    if (!shouldTrack || activeEffect === undefined) {
        return;
    }
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
        if (activeEffect.options.onTrack) {
            activeEffect.options.onTrack({
                effect: activeEffect,
                target,
                type,
                key
            });
        }
    }
}

function trigger(target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        // never been tracked
        return;
    }
    const effects = new Set();
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                if (effect !== activeEffect || effect.allowRecurse) {
                    effects.add(effect);
                }
            });
        }
    };
    if (type === "clear" /* CLEAR */) {
        // collection being cleared
        // trigger all effects for target
        depsMap.forEach(add);
    }
    else if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
            if (key === 'length' || key >= newValue) {
                add(dep);
            }
        });
    }
    else {
        // schedule runs for SET | ADD | DELETE
        if (key !== void 0) {
            add(depsMap.get(key));
        }
        // also run for iteration key on ADD | DELETE | Map.SET
        switch (type) {
            case "add" /* ADD */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                break;
            case "set" /* SET */:
                if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY));
                }
                break;
        }
    }
    const run = (effect) => {
        if (effect.options.onTrigger) {
            effect.options.onTrigger({
                effect,
                target,
                key,
                type,
                newValue,
                oldValue,
                oldTarget
            });
        }
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        }
        else {
            effect();
        }
    };
    effects.forEach(run);
}

这段源码涉及到Vue3响应的核心track和trigger,Vue3通过这2个方法来代替Vue2中的watcher,我们给customRef函数传入函数参数,通过CustomRefImpl的构造函数注入了track函数和trigger函数。track和trigger这里简单说明下,后面的篇章会详细介绍这2个函数。简单来说track是跟踪,get方法中调用track函数来收集依赖,其中重要的变量:

  1. targetMap:是一个weakMap 跟踪缓存,key当前对象,value是一个所有订阅该对象的Map也就是depsMap
  2. depsMap:是一个Map类型,key为当前对象的某个属性这里是value属性(ref的value属性值),depsMap的value存储的是一个Set数组,里面存放着当前的激活的副作用activeEffect
  3. activeEffect当前激活的副作用,也就是订阅者。

再说一下trigger,通过const depsMap = targetMap.get(target);找到该对象的所有订阅者,在执行if (key !== void 0) { add(depsMap.get(key)); }找到该key也就是ref的value属性对应的订阅者加入到effects数组中,最后通过run方法执行effect。通过effect重新渲染界面更新数据,之后的篇章会对整个更新过程进行详细的讲解,这里就简单介绍一下trigger和track,更复杂的effect暂时不在这里展开了。总结来说就是track的作用是收集谁依赖与我,trigger的作用就是我变化了,从谁依赖我的数组中,拿出来一个一个去改变!

13.shallowRef

官网解释:创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。初看确实有点拗口,简单来说就是shallowRef对象时可以追踪对象里的变化,但是不再为对象添加proxy代理。所以只有自身可以追踪。 结合Ref看就一目了然了,代码如下(示例):

const { shallowRef, ref, isReactive } = Vue;

const petShallow = shallowRef({
    name: "皮卡丘"
});
console.log(petShallow.value); // {name: "皮卡丘"}
console.log(isReactive(petShallow.value)) // false
petShallow.value = {
    name: "杰尼龟"
};
console.log(petShallow.value) // {name: "杰尼龟"}
console.log(isReactive(petShallow.value)) //false

const petRef = ref({
    name: "皮卡丘"
});
console.log(petRef.value); // 皮卡丘 Proxy {name: "皮卡丘"}
console.log(isReactive(petRef.value)) // true
petRef.value = {
    name: "杰尼龟"
};
console.log(petRef.value) // 杰尼龟 Proxy {name: "杰尼龟"}
console.log(isReactive(petRef.value)) // true

源码

function shallowRef(value) {
        return createRef(value, true);
}
 function createRef(rawValue, shallow = false) {
     if (isRef(rawValue)) {
         return rawValue;
     }
     return new RefImpl(rawValue, shallow);
 }
 class RefImpl {
     constructor(_rawValue, _shallow = false) {
         this._rawValue = _rawValue;
         this._shallow = _shallow;
         this.__v_isRef = true;
         this._value = _shallow ? _rawValue : convert(_rawValue);
     }
     get value() {
         track(toRaw(this), "get" /* GET */, 'value');
         return this._value;
     }
     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);
         }
     }
 }
 const convert = (val) => isObject(val) ? reactive(val) : val;

这段源码是不是相当熟悉,其实这段源码和ref的源码一样,区别在于this._shallow = _shallow this._value = _shallow ? _rawValue : convert(_rawValue);这2个地方,从第二也就能看出与ref的区别来,当传入的值是对象时,不会再去调用reactive。

14.triggerRef

官方解释:手动执行与 shallowRef 关联的任何副作用。 简单的来说就是激活所有他的订阅者代码如下(示例):

const { triggerRef, watchEffect, shallowRef } = Vue;

const petShallow = shallowRef({
    name: "皮卡丘"
});
// 第一次运行时记录一次 "Hello, world"
watchEffect(() => {
    console.log(petShallow.value.name)
})
petShallow.value.name = "杰尼龟";

triggerRef(petShallow);

源码

function triggerRef(ref) {
    trigger(toRaw(ref), "set" /* SET */, 'value', ref.value );
}
function toRaw(observed) {
    return ((observed && toRaw(observed["__v_raw" /* RAW */])) || observed);
}

源码很简单,其实就是手动执行trigger,之前我们介绍过这个函数了,下面的代码可以看到RefImpl类的get会调用track函数收集订阅者,所以执行trigger函数时,就会更新值。

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

15.computed

官方解释:接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象,或者,接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。其实和之前的OptionAPI中的computed没什么区别,用法一样。代码如下(示例):

const { computed, reactive } = Vue;

const pet = reactive({
    name: "皮卡丘"
});
const addDesc = computed(() => pet.name = `打工人${pet.name}`);
console.log(addDesc.value);// 打工人皮卡丘
pet.name = "杰尼龟";
console.log(addDesc.value); // 打工人杰尼龟

源码

function computed$1(getterOrOptions) {
    const c = computed(getterOrOptions);
    recordInstanceBoundEffect(c.effect);
    return c;
}
function computed(getterOrOptions) {
    let getter;
    let setter;
    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions;
        setter = () => {
            console.warn('Write operation failed: computed value is readonly');
        }
        ;
    }
    else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set);
}

第一步是根据参数getterOrOptions 是否是函数来确定是只读还是get和set都存在,设置好getter和setter后构建ComputedRefImpl,我们具体看下一这个类

class ComputedRefImpl {
    constructor(getter, _setter, isReadonly) {
        this._setter = _setter;
        this._dirty = true;
        this.__v_isRef = true;
        this.effect = effect(getter, {
            lazy: true,
            scheduler: () => {
                if (!this._dirty) {
                    this._dirty = true;
                    trigger(toRaw(this), "set" /* SET */, 'value');
                }
            }
        });
        this["__v_isReadonly" /* IS_READONLY */] = isReadonly;
    }
    get value() {
        // the computed ref may get wrapped by other proxies e.g. readonly() #3376
        const self = toRaw(this);
        if (self._dirty) {
            self._value = this.effect();
            self._dirty = false;
        }
        track(self, "get" /* GET */, 'value');
        return self._value;
    }
    set value(newValue) {
        this._setter(newValue);
    }
}

设置了_dirty = true 标志是否需要更新,__v_isRef = true所有我们对computed的读取都是通过.value就是因为他属于ref类型,其次定义了computed对value的get和set方法,这里面computed响应的核心effect,这里不过多介绍,因为后面的章节会单独介绍Vue3中响应式的过程。这里简单说一下,我们之前介绍过在在获取值时会调用track来收集订阅者也就是我们这里的各类effect,而更新值时会调用trigger来调用收集到的effect。执行effect时会执行其中的scheduler函数,computed的scheduler就继续调用trigger函数,直到调用到最外层的渲染Effect结束,渲染effect会调用computed的get方法获取最新值。

this.effect = effect(getter, {
   lazy: true,
   scheduler: () => {
       if (!this._dirty) {
           this._dirty = true;
           trigger(toRaw(this), "set" /* SET */, 'value');
       }
   }
});

16.watchEffect,watch

watchEffect,watch 监听值得变化,watchEffect相对是简单版本的watch,watchEffect会立即执行传入的函数,而watch并不会。代码如下(示例):

const { watchEffect, reactive, watch } = Vue;
const pet = reactive({
    name: "皮卡丘"
});
const addDescEffectWatch = watchEffect(() => {
    console.log(`打工人${pet.name}`)
});
const addDescWatch = watch(pet, (pet, oldPet) => {
    console.log(`打工人${pet.name}`)
});
pet.name = "杰尼龟";
// 一共打印3次effectWatch会立即执行一次,所以打印2个,watch并不会立即执行所以执行一次
// 打工人皮卡丘
// 打工人杰尼龟
// 打工人杰尼龟

源码

function watch(source, cb, options) {
    if (!isFunction(cb)) {
        warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
            `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
            `supports \`watch(source, cb, options?) signature.`);
    }
    // watch 传入的source 是 ref或者reactive对象或者一个函数 ,cb为值变动的回调函数,option配置deep immediate等
    return doWatch(source, cb, options);
}
function watchEffect(effect, options) {
  // watchEffect 传入的source是effect 函数,不支持传入回调函数
  return doWatch(effect, null, options);
}
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) {
	// watchEffect 不支持immediate 和 deep
   if (!cb) {
       if (immediate !== undefined) {
           warn(`watch() "immediate" option is only respected when using the ` +
               `watch(source, callback, options?) signature.`);
       }
       if (deep !== undefined) {
           warn(`watch() "deep" option is only respected when using the ` +
               `watch(source, callback, options?) signature.`);
       }
   }
   // 定义一个警告的函数,用于抛出警告
   const warnInvalidSource = (s) => {
       warn(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, ` +
           `a reactive object, or an array of these types.`);
   };
   // 根据不同的source类型进行不同的逻辑处理,这里我们可以看出,不单单可以传入函数,还可以传入ref、reactive、数组,代码会为我们
   // 创建不同的getter函数,ref和reactive创建的函数是获取自身的值,如果是数组会遍历每一个值,通过类型创建不同的getter
   let getter;
   let forceTrigger = false;
   if (isRef(source)) {
       getter = () => source.value;
       forceTrigger = !!source._shallow;
   }
   else if (isReactive(source)) {
       getter = () => source;
       deep = true;
   }
   else if (isArray(source)) {
       getter = () => source.map(s => {
           if (isRef(s)) {
               return s.value;
           }
           else if (isReactive(s)) {
               return traverse(s);
           }
           else if (isFunction(s)) {
               return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */, [
                   instance && instance.proxy
               ]);
           }
           else {
               warnInvalidSource(s);
           }
       });
   }
   // 如果是函数有2中情况,一种是watch传入了函数监听某个getter函数,另一种就是watchEffect
   else if (isFunction(source)) {
       if (cb) { // 如果是watch
           // getter with cb
           getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */, [
               instance && instance.proxy
           ]);
       }
       else {
           // no cb -> simple effect 如果是watchEffect
           getter = () => {
               if (instance && instance.isUnmounted) {
                   return;
               }
               if (cleanup) {
                   cleanup();
               }
               return callWithAsyncErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onInvalidate]);
           };
       }
   }
   else {
       getter = NOOP;
       warnInvalidSource(source);
   }
   if (cb && deep) { // 如果是深层
       const baseGetter = getter;
       getter = () => traverse(baseGetter());
   }
   let cleanup;
   let onInvalidate = (fn) => {
       cleanup = runner.options.onStop = () => {
           callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
       };
   };
   // 初始化默认的旧值 INITIAL_WATCHER_VALUE = {}
   let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE;
   // 定义 副作用函数effect的scheduler,当副作用effect执行时执行scheduler更新
   const job = () => {
       if (!runner.active) {
           return;
       }
       // 如果是watch   
       if (cb) {
           // watch(source, cb)
           const newValue = runner();
           // 深层,或者开启强制触发,或者值发生了变动,都需要执行回调函数
           if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
               // cleanup before running cb again
               if (cleanup) {
                   cleanup();
               }
               // 执行回调函数,callWithAsyncErrorHandling只是在封装了一层错误处理
               callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
                   newValue,
                   // 第一次更改将undefined 作为旧值
                   // pass undefined as the old value when it's changed for the first time
                   oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
                   onInvalidate
               ]);
               oldValue = newValue;
           }
       }
       else {
           // watchEffect
           runner();
       }
   };
   // important: mark the job as a watcher callback so that scheduler knows
   // it is allowed to self-trigger (#1727)
   job.allowRecurse = !!cb;
   let scheduler;
   if (flush === 'sync') {
       scheduler = job;
   }
   else if (flush === 'post') {
       scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
   }
   else {
       // default: 'pre'
       scheduler = () => {
           if (!instance || instance.isMounted) {
               queuePreFlushCb(job);
           }
           else {
               // with 'pre' option, the first call must happen before
               // the component is mounted so it is called synchronously.
               job();
           }
       };
   }
   // 创建effect
   const runner = effect(getter, {
       lazy: true,
       onTrack,
       onTrigger,
       scheduler
   });
   recordInstanceBoundEffect(runner, instance);
   // initial run 如果是watch
   if (cb) {
   		// option传入立即执行,直接执行job ,否则就执行runner创建effect等待后续trigger触发effect函数
       if (immediate) {
           job();
       }
       else {
       		// 执行runner,runner会调用之前定义的getter,通过getter去做track和trigger
           oldValue = runner();
       }
   }
   else if (flush === 'post') {
       queuePostRenderEffect(runner, instance && instance.suspense);
   }
   else {
   	   // watchEffect 初次直接执行runner
       runner();
   }
   return () => {
       stop(runner);
       if (instance) {
           remove(instance.effects, runner);
       }
   };
}

watch,watchEffect的源码比较多,核心部分和computed有点类似都是track,trigger,effect相关的部分,所以下一篇打算单独来讲讲Vue3 和响应核心track 追踪 triiger 触发器和副作用函数effect,无论是界面渲染,还是watch,watchEffect,computed都离不开effect函数的创建,所以在一下篇章将重点对track,trigger,effect这3个函数进行讲解。

总结

以上就是今天要讲的内容,对所有reactiveAPI做了一个基本的源码介绍,如果有哪里不明白或不清楚欢迎留言反馈~,你的支持就是我更新的动力。给个三连吧,求求了,这个对我真的很重要
在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小疯疯0413

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值