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函数来收集依赖,其中重要的变量:
- targetMap:是一个weakMap 跟踪缓存,key当前对象,value是一个所有订阅该对象的Map也就是depsMap
- depsMap:是一个Map类型,key为当前对象的某个属性这里是value属性(ref的value属性值),depsMap的value存储的是一个Set数组,里面存放着当前的激活的副作用activeEffect
- 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做了一个基本的源码介绍,如果有哪里不明白或不清楚欢迎留言反馈~,你的支持就是我更新的动力。给个三连吧,求求了,这个对我真的很重要