深入分析对于map、set、weakmap、weakset的响应式拦截
在上篇的内容中我们以reactive为起点分析了reactivity对于array和object的拦截,本文我们继续以reactive为起点分析map、set、weakmap、weakset等数据结构的响应式拦截。
export function shallowreactive(target) {
return createreactiveobject(
target,
false,
shallowreactivehandlers,
shallowcollectionhandlers,
shallowreactivemap
);
}
export function readonly(target) {
return createreactiveobject(
target,
true,
readonlyhandlers,
readonlycollectionhandlers,
readonlymap
);
}
export function shallowreadonly(target) {
return createreactiveobject(
target,
true,
shallowreadonlyhandlers,
shallowreadonlycollectionhandlers,
shallowreadonlymap
);
}
export function reactive(target) {
//如果被代理的是readonly返回已经被readonly代理过的target
if (isreadonly(target)) {
return target;
}
return createreactiveobject(
target,
false,
mutablehandlers,
mutablecollectionhandlers,
reactivemap
);
}
- 之前我们分析了mutablehandlers、shallowreadonlyhandlers、readonlyhandlers、shallowreactivehandlers,但是还有一个部分是没有分析的也就是对于集合类型的处理mutablecollectionhandlers、shallowreadonlycollectionhandlers、readonlycollectionhandlers、shallowcollectionhandlers下面我们看看这四个对象的庐山真面目吧!
const mutablecollectionhandlers = {
get: createinstrumentationgetter(false, false),
};
const shallowcollectionhandlers = {
get: createinstrumentationgetter(false, true),
};
const readonlycollectionhandlers = {
get: createinstrumentationgetter(true, false),
};
const shallowreadonlycollectionhandlers = {
get: createinstrumentationgetter(true, true),
};
- 我们可以看到所有的collectionhandlers都是由工厂函数createinstrumentationgetter创建的,这里与之前的handlers不同,所有的拦截都只有一个方法了那就是get,这是因为对于map set等数据结构的操作与object和array的操作是不同的,对于set需要调用add,delete,has等方法map需要调用set,delete,has等方法所以不能直接对集合数据类型进行操作,那么我们就只需要拦截get获取到当前集合调用的方法然后对这个方法进行拦截就可以了。
function createinstrumentationgetter(isreadonly, shallow) {
const instrumentations = shallow
? isreadonly
? shallowreadonlyinstrumentations
: shallowinstrumentations
: isreadonly
? readonlyinstrumentations
: mutableinstrumentations;
return (target, key, receiver) => {
//对于map set的代理同样需要添加
if (key === is_reactive) {
return !isreadonly;
} else if (key === is_readonly) {
return isreadonly;
} else if (key === raw) {
return target;
}
//通过之前生成的拦截方法进行调度
return reflect.get(
hasown(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
);
};
}
- 对于和之前相同的属性判断我们就不再赘述了,直接看mutableinstrumentations、readonlyinstrumentations、shallowinstrumentations、shallowreadonlyinstrumentations通过readonly和shallow的不同得到不同的处理器。那我们就需要看看这四个对象是如何生成的了。
//通过拦截map set的方法实现代理
export function createinstrumentations() {
const mutableinstrumentations = {
};
const shallowinstrumentations = {
};
const readonlyinstrumentations = {
};
const shallowreadonlyinstrumentations = {
};
//其中keys,values,entries,symbol.iterator是通过
//迭代器运行的,需要进行拦截
const iteratormethods = ["keys", "values", "entries", symbol.iterator];
iteratormethods.foreach((method) => {
mutableinstrumentations[method] = createiterablemethod(
method,
false,
false
);
readonlyinstrumentations[method] = createiterablemethod(
method,
true,
false
);
shallowinstrumentations[method] = createiterablemethod(method, false, true);
shallowreadonlyinstrumentations[method] = createiterablemethod(
method,
true,
true
);
});
return [
mutableinstrumentations,
readonlyinstrumentations,
shallowinstrumentations,
shallowreadonlyinstrumentations,
];
}
下面我们需要将内容分成四个部分,分别解读这四个对象的方法实现。
(1).mutableinstrumentations
const mutableinstrumentations = {
get(key) {
return get(this, key);
},
get size() {
return size(this);
},
has: has,
add,
set: set,
delete: deleteentry,
clear,
foreach: createforeach(false, false),
};
- 对于mutableinstrumentations的实现有get方法,这其实就是获取元素的方法,我们需要对这个方法进行拦截。
- 简单的说,其实就是对set map的操作方法进行拦截,然后在获取值的时候进行收集依赖,在修改值的时候触发依赖核心依然没有改变。但是需要注意的是map的的key可以是对象,还有可能是代理对象,但是无论是对象还是代理对象我们都应该只能访问到唯一的那个值。
下面我们开始解读get方法。
//代理map set weakmap weakset的get方法
function get(target, key, isreadonly = false, isshallow = false) {
target = target[raw];
//因为map的key可以是对象,所以需要rawkey
//同时收集依赖必须要rawtarget
const rawtarget = toraw(target);
const rawkey = toraw(key);
if (!isreadonly) {
/**
* 为了实现在effect函数中无论是使用了以proxykey
* 还是以rawkey为键进行收集的依赖,在effect外部
* 修改proxymap的proxykey或rawkey都能触发依赖
* 更新,当使用proxykey为键时,需要进行两次track
* 例如:当前在effect中获取的是proxykey那么进行
* 两次track,在depsmap中就会有两个entries,分别
* 是以rawkey和proxykey指向的deps但是指向的deps
* 不改变 那么在set中修改值的时候,无论是修改的
* proxykey还是rawkey都能在depsmap中找到正确的
* 依赖进行更新
*/
if (key !== rawkey) {
track(rawtarget, trackoptypes.get, key);
}
track(rawtarget, trackoptypes.get, rawkey);
}
const { has } = getproto(rawtarget);
const wrap = isshallow ? toshallow : isreadonly ? toreadonly : toreactive;
//无论是使用rawkey还是key都能读取到
if (has.call(rawtarget, key)) {
//仅需进行代理,并且返回代理后的对象
return wrap(target.get(key));
} else if (has.call(rawtarget, rawkey)) {
return wrap(target.get(rawkey));
} else if (target !== rawtarget) {
target.get(key);
}
}
- 我们可以发现依赖收集触发了两次,当proxykey为key的时候需要多触发一次依赖收集,这是为了保证后续无论是通过rawkey修改值还是通过proxykey修改值最终都能触发到依赖。
- 同样我们处在get当中,无论访问proxykey还是rawkey我们都只能返回唯一的值。所以做了if elseif的判断。
接下来继续分析size方法:
//对map set的size属性的拦截
function size(target, isreadonly = false) {
target = target[raw];
!isreadonly && track(toraw(target), trackoptypes.iterate, iterate_key);
return reflect.get(target, trackoptypes.size, target);
}
- size属于属性的访问,所以肯定是进行track,这里的target都会调用toraw,之前在proxy中传递给我们的对象本来就是代理前的对象所以不需要toraw,但是当前我们是对方法进行的拦截所以this访问到的是代理后的对象所以需要对对象进行还原。
- 这里就是对 "iterate" 进行了收集依赖,也就是说如果说执行set delete add clear都会触发这个依赖。具体可以看看后面对于这几个方法的实现。
下面继续分析has方法:
//has进行依赖收集
function has(key, isreadonly = false) {
const target = this[raw];//获取代理前的对象
const rawtarget = toraw(target);
const rawkey = toraw(key);//获取代理前的key
if (!isreadonly) {
//这里执行两次track的原因和上面相同
if (key !== rawkey) {
//收集依赖,类型为"has"
track(rawtarget, trackoptypes.has, key);
}
track(rawtarget, trackoptypes.has, rawkey);
}
return key === rawkey
? target.has(key)
: target.has(key) || target.has(rawkey);
}
- 其实这个type主要是传递上下文信息到ontrigger中(如果effect中有这个函数),所以本质都是通过target和key收集依赖。这个函数很简单就不在过多描述了。
继续add的分析:
//对set的add方法的拦截
function add(value) {
value = toraw(value); //获取rawvalue
const target = toraw(this); //获取rawtarget
const proto = getproto(target);
//如果不存在这个值则是修改进行trigger
const hadkey = proto.has.call(target, value);
if (!hadkey) {
target.add(value);
trigger(target, triggeroptypes.add, value, value);
}
return this;
}
我们来看看对于 "add" 类型的trigger处理:
case triggeroptypes.add:
if (!isarray(target)) {
//map weakmap object
deps.push(depsmap.get(iterate_key));
if (ismap(target)) {
deps.push(depsmap.get(map_key_iterate_key));
}
} else if (isintegerkey(key)) {
//当前修改的是数组且是新增值
//例如 arr.length = 3 arr[4] = 8
//此时数组长度会发生改变所以当前数组的
//length属性依然需要被放入依赖
deps.push(depsmap.get("length"));
}
break;
- 触发关于迭代器的依赖,例如在effect中执行了object.keys map.entries map.keys等方法,那么iterate_key、map_key_iterate_key就会收集到相应的依赖函数。 继续set的分析:
//这里的key可能是rawkey 也可能是proxykey
function set(key, value) {
value = toraw(value); //获取原始的value值
const target = toraw(this); //获取原始的target
const { has, get } = getproto(target);
//判断当前使用的key能否获得值
let hadkey = has.call(target, key);
//获取不到可能是proxykey,转化为rawkey再试试
if (!hadkey) {
key = toraw(key);
hadkey = has.call(target, key);
} else {
checkidentitykeys(target, has, key);
}
//通过key获取
const oldvalue = get.call(target, key);
//设置
target.set(key, value);
//rawkey和proxykey都获取不到则是添加属性
if (!hadkey) {
//触发更新
trigger(target, triggeroptypes.add, key, value);
}
//修改属性
else if (haschanged(value, oldvalue)) {
trigger(target, triggeroptypes.set, key, value, oldvalue);
}
return this;
}
与object和array类似,但是依然需要处理proxykey和rawkey的问题,如果proxykey读取到了值则不使用rawkey如果读取不到转化为rawkey继续读取,然后根据hadkey判断是增加还是修改。
继续分析delete 和 clear:
function deleteentry(key) {
const target = toraw(this);
const { has, get } = getproto(target);
//删除的key可能是proxykey也可能是rawkey
//所以需要判断,判断的时候时候需要使用has
//方法,所以需要对target还原,实际上所有的
//操作都不能使用receiver,会造成二次依赖触发
let hadkey = has.call(target, key);
if (!hadkey) {
key = toraw(key);
hadkey = has.call(target, key);
} else {
checkidentitykeys(target, has, key);
}
const oldvalue = get ? get.call(target, key) : undefined;
const result = target.delete(key);
//删除触发更新
if (hadkey) {
trigger(target, triggeroptypes.delete, key, undefined, oldvalue);
}
return result;
}
function clear() {
const target = toraw(this);
const haditems = target.size !== 0;
//执行clear后 数据会被全部清空,oldtarget将不再存在
//所以需要浅克隆保证旧数据依然能进入trigger
const oldtarget = ismap(target) ? new map(target) : new set(target);
const result = target.clear();
if (haditems) {
trigger(target, triggeroptypes.clear, undefined, undefined, oldtarget);
}
return result;
}
- delete和clear都是删除元素,所以是触发依赖,看看trigger对于delete和clear的类型的处理:
//clear
if (type === triggeroptypes.clear) {
//清空,相当于所有的元素都发生改变
//故而全部都需要添加进依赖
deps = [...depsmap.values()];
}
//delete
case triggeroptypes.delete:
if (!isarray(target)) {
deps.push(depsmap.get(iterate_key));
if (ismap(target)) {
deps.push(depsmap.get(map_key_iterate_key));
}
}
break;
- 对于clear因为所有元素都被删除了,所以所有元素的依赖都需要被触发。
- 对于delete,则是触发执行了foreach、entries keys values等方法的依赖。当然删除元素本身的依赖同样需要被执行。
最后一个foreach:
function createforeach(isreadonly, isshallow) {
return function foreach(callback, thisarg) {
const observed = this;
const target = observed["__v_raw" /* reactiveflags.raw */];
const rawtarget = toraw(target);
const wrap = isshallow ? toshallow : isreadonly ? toreadonly : toreactive;
!isreadonly &&
track(rawtarget, "iterate" /* trackoptypes.iterate */, iterate_key);
return target.foreach((value, key) => {
return callback.call(thisarg, wrap(value), wrap(key), observed);
});
};
}
- 当调用了foreach函数 也就是map.foreach或者set.foreach,这个也是靠迭代器所以依赖的收集则是iterate_key。 好了,到目前为止所有的api都已经分析完成了。收集依赖的方法是get has size foreach entries keys values,触发依赖则是clear set delete add。foreach、size、entries、keys、values方法会收集iterate_key或map_key_iterate_key的依赖。delete add set则会调用迭代器的依赖,换句话说就是集合的元素增加减少都会调用迭代器收集的依赖。
(2).shallowinstrumentations
const shallowinstrumentations = {
get(key) {
return get(this, key, false, true);
},
get size() {
return size(this);
},
has: has,
add,
set: set,
delete: deleteentry,
clear,
foreach: createforeach(false, true),
};
- 传递readonly、shallow生成不同的get和foreach。
(3).readonlyinstrumentations
const readonlyinstrumentations = {
get(key) {
return get$1(this, key, true);
},
get size() {
return size(this, true);
},
has(key) {
return has.call(this, key, true);
},
//只读的属性是不需要修改的,全部通过warn提示
add: createreadonlymethod(triggeroptypes.add),
set: createreadonlymethod(triggeroptypes.set),
delete: createreadonlymethod(triggeroptypes.delete),
clear: createreadonlymethod(triggeroptypes.clear),
foreach: createforeach(true, false),
};
function createreadonlymethod(type) {
return function (...args) {
{
const key = args[0] ? `on key "${args[0]}" ` : ``;
console.warn(
`${shared.capitalize(
type
)} operation ${key}failed: target is readonly.`,
toraw(this)
);
}
return type === triggeroptypes.delete ? false : this;
};
}
- 对于readonly类型不能够修改所以只要访问set add delete clear等方法就会发出警告并且不能修改。
(4).shallowreadonlyinstrumentations
const shallowreadonlyinstrumentations = {
get(key) {
return get(this, key, true, true);
},
get size() {
return size(this, true);
},
has(key) {
return has.call(this, key, true);
},
//只读的属性是不需要修改的,全部通过warn提示
add: createreadonlymethod(triggeroptypes.add),
set: createreadonlymethod(triggeroptypes.set),
delete: createreadonlymethod(triggeroptypes.delete),
clear: createreadonlymethod(triggeroptypes.clear),
foreach: createforeach(true, true),
};
与第三种情况相同。
当然对于entries values keys symbol.iterator的拦截还没有分析,我们继续看看实现的源码:
function createiterablemethod(method, isreadonly, isshallow) {
return function (...args) {
const target = this[raw];
const rawtarget = toraw(target);
const targetismap = ismap(rawtarget); //被代理对象是否是map
//如果是entries方法,会返回key和value
const ispair =
method === "entries" || (method === symbol.iterator && targetismap);
const iskeyonly = method === "keys" && targetismap;
//调用这个方法,返回迭代器
const inneriterator = target[method](...args);
//获取当前需要代理的函数
const wrap = isshallow ? toshallow : isreadonly ? toreadonly : toreactive;
//readonly不需要track
!isreadonly &&
//追踪
track(
rawtarget,
trackoptypes.iterate,
//如果是map且访问的keys方法则是map_key_iterate_key
iskeyonly ? map_key_iterate_key : iterate_key
);
return {
//重写迭代器方法 key,value还可以被深度代理
next() {
const { value, done } = inneriterator.next();
return done
? { value, done }
: {
//如果是entries方法value则是key和value
value: ispair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done,
};
},
[symbol.iterator]() {
return this;
},
};
};
}
总结一下:对于map set weakmap weakset的拦截,主要处理的有两个地方:
- 第一:对于map和weakmap类型,他们的key可能是一个对象,那么对象就可能是被代理过的对象,但是无论通过proxykey访问还是rawkey访问到的对象都是一样的,同样的在effect中使用proxykey,那么会触发依赖收集,这个时候会存放进行两次track,保证在effect外部修改proxy值的时候,无论是使用proxykey修改还是rawkey修改最后都能正确触发依赖。
- 第二:当时用entries keys values foreach等集合方法的时候,收集依赖的key则是iterate_key或map_key_iterate_key,当进行add delete set操作的时候会多添加在iterate_key和map_key_iterate_key时收集到的依赖,保证了即使使用集合方法或者迭代器依然能够进行依赖收集和触发。
- 第三:整个reactivity的核心依然没有改变,只是拦截变成了拦截操作数据的方法,依旧是访问的时候收集依赖,修改的时候触发依赖。
ref、computed等方法的实现
(1).ref与shallowref源码解析
上面我们讲述了对于对象数组等数据的代理,但是如果是string、number等基本数据类型呢?我们就需要采用ref这个api来实现代理了。我们先来看看ref与shallowref的源码实现:
//判断当前r是否是ref
function isref(r) {
//根本就是判断当前对象上是否有__v_isref属性
return !!(r && r.__v_isref === true);
}
function ref(value) {
//创建ref的工厂函数,第二个参数为是为为shallow
return createref(value, false);
}
function shallowref(value) {
//第二个参数为true表示当前是shallow
return createref(value, true);
}
//如果是ref则返回ref,只对非ref进行代理
function createref(rawvalue, shallow) {
if (isref(rawvalue)) {
return rawvalue;
}
return new refimpl(rawvalue, shallow);
}
这一段代码非常简单,就是通过工厂函数 createref(value,isshallow) 传递当前需要代理的基本数据类型以及是否只需要代理第一层。我们接着向下分析,看看refimpl实现吧!。
class refimpl {
constructor(value, __v_isshallow) {
//是否由shallowref创建
this.__v_isshallow = __v_isshallow;
//这个dep和target,key对应的dep是一个意思
//可以理解为target = this;key="value"对应的dep
this.dep = undefined;
this.__v_isref = true;//是否是ref
//未代理的value
this._rawvalue = __v_isshallow ? value : toraw(value);
//代理过后的value
this._value = __v_isshallow ? value : toreactive(value);
}
get value() {
//收集所有的依赖
trackrefvalue(this);
return this._value;
}
set value(newval) {
//是否还需要进行深度代理
const usedirectvalue = this.__v_isshallow || isshallow(newval) || isreadonly(newval);
newval = usedirectvalue ? newval : toraw(newval);
//如果当前值发生了修改相当于object.is
if (shared.haschanged(newval, this._rawvalue)) {
this._rawvalue = newval;
this._value = usedirectvalue ? newval : toreactive(newval);
//触发依赖更新
triggerrefvalue(this, newval);
}
}
}
//两个工具函数
const toreactive = (value) => shared.isobject(value) ? reactive(value) : value;
const toreadonly = (value) => shared.isobject(value) ? readonly(value) : value;
- 我们可以发现这里的拦截只有get和set了,当然也不需要deleteproperty has ownkeys的拦截了,所以我们通过类自带的拦截器进行拦截,同样的逻辑get的时候收集依赖,set的时候触发依赖。
function trackrefvalue(ref) {
//判断当前activeeffect是否存在不存在则不需要收集依赖
if (shouldtrack && activeeffect) {
ref = toraw(ref);
//收集target为ref key为"value"的依赖
trackeffects(ref.dep || (ref.dep = createdep()), {
target: ref,//target相当于ref
type: "get",//类型是"get"
key: 'value'//key是"value"
});
}
}
function triggerrefvalue(ref, newval) {
ref = toraw(ref);
if (ref.dep) {
//触发target为ref key为"value"的依赖
triggereffects(ref.dep, {
target: ref,
type: "set" /* triggeroptypes.set */,
key: 'value',
newvalue: newval
});
}
}
- 我们可以发现整个ref的设计相当的简单,就是把需要代理的基本数据类型变为一个对象,然后再代理key为value值。
(2).torefs
这是为了解决解构之后的proxy失去代理作用的api,例如:
const proxy = reactive({a:1,b:2})
const {a,b} = proxy //失效
这样就失效了,但是如果你代理的是两层解构是不会出现proxy失效的,例如:
const proxy = reactive({a:{a:1},b:{b:1}})
const {a,b} = proxy //a,b依然是响应式的
好了,为了解决第一种情况,torefs出来了。
function torefs(object) {
//如果不是代理过的对象,不能使用torefs
if (!isproxy(object)) {
console.warn(`torefs() expects a reactive object but received a plain one.`);
}
//创建容器
const ret = isarray(object) ? new array(object.length) : {};
//将解构后的值变为响应式赋值给ret容器
for (const key in object) {
toref返回objectrefimpl实例返回一个对象
ret[key] = toref(object, key);
}
return ret;
}
//将代理的值变为ref
function toref(object, key, defaultvalue) {
const val = object[key];
return isref(val)
? val
: new objectrefimpl(object, key, defaultvalue);
}
//objectrefimpl实例访问value的时候相当于是
//访问的proxy[key]这样就依旧是响应式的
//同理设置的时候proxy[key] = xxx也是响应式的
//我们只需要访问.value和设置.value就可以了
class objectrefimpl {
constructor(_object, _key, _defaultvalue) {
//存储proxy
this._object = _object;
//存储key
this._key = _key;
this._defaultvalue = _defaultvalue;
this.__v_isref = true;//当前是ref
}
get value() {
//this._object[this._key]相当于读取了proxy中的值
//会收集依赖
const val = this._object[this._key];
return val === undefined ? this._defaultvalue : val;
}
set value(newval) {
//设置了proxy中的值触发依赖更新
this._object[this._key] = newval;
}
}
torefs就是在解构之前,把要访问的值变成一个对象,也就是说 {a} = torefs(proxy) 中的a就是objectrefimpl实例,那么访问 .value 就会去访问 proxy[key] 这样就可以收集依赖,set的时候就会触发依赖。
(4).computed
这是一个计算属性的api,我们可以通过访问computed返回值的value属性获取最新的计算结果,并且computed返回值依然是响应式的,可以在effect中收集依赖,修改value属性的时候能触发依赖更新。
//对传递的参数进行整理生成computedrefimpl实例并返回
function computed(getteroroptions, debugoptions, isssr = false) {
let getter;
let setter;
//第一个参数是函数,则只有getter没有setter
const onlygetter = shared.isfunction(getteroroptions);
if (onlygetter) {
getter = getteroroptions;
setter = () => {
console.warn('write operation failed: computed value is readonly');
};
}
else {
//获取getter和setter
//getter返回一个计算值
//如果setter存在当修改computedrefimpl实例的value属性
//的时候会调用setter并把修改的值传递到setter中
getter = getteroroptions.get;
setter = getteroroptions.set;
}
//创建实例
const cref = new computedrefimpl(getter, setter, onlygetter || !setter, isssr);
if (debugoptions && !isssr) {
cref.effect.ontrack = debugoptions.ontrack;
cref.effect.ontrigger = debugoptions.ontrigger;
}
return cref;
}
- computed本身只是对传递的参数进行了整理,然后创建了computedrefimpl实例并且返回。
_a = "__v_isreadonly"
class computedrefimpl {
constructor(getter, _setter, isreadonly, isssr) {
this._setter = _setter;
this.dep = undefined;
this.__v_isref = true;
this[_a] = false;
this._dirty = true;
//这里的逻辑reactivity上篇中已经讲过了
this.effect = new reactiveeffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
triggerrefvalue(this);
}
});
//在trigger中优先触发有computed属性的effect
this.effect.computed = this;
this.effect.active = this._cacheable = !isssr;
this["__v_isreadonly"] = isreadonly;
}
get value() {
const self = toraw(this);
trackrefvalue(self);
if (self._dirty || !self._cacheable) {
self._dirty = false;
self._value = self.effect.run();
}
return self._value;
}
set value(newvalue) {
this._setter(newvalue);
}
}
在construtor中创建reactiveeffect实例,第二个函数代表的是schduler调度器,如果有这个函数,那么触发依赖的时候将不会调用run方法而是调用schduler,所以如果调用这个函数表示computed中的getter中的某个代理属性发生了改变.然后 _dirty = true 表示值发生了改变,那么computedrefimpl收集到的依赖将会被触发,同样的computedrefimpl的依赖是在访问computedrefimpl的value属性的时候收集到的。
(5)其他api源码
最后还有customref以及deferredcomputed大家看看源码吧,不在进行讲解了。
1.customref的实现
//customref的实现
function customref(factory) {
return new customrefimpl(factory);
}
class customrefimpl {
constructor(factory) {
this.dep = undefined;
this.__v_isref = true;
const { get, set } = factory(
() => trackrefvalue(this),
() => triggerrefvalue(this)
);
this._get = get;
this._set = set;
}
get value() {
return this._get();
}
set value(newval) {
this._set(newval);
}
}
2.deferredcomputed的实现
function deferredcomputed(getter) {
return new deferredcomputedrefimpl(getter);
}
class deferredcomputedrefimpl {
constructor(getter) {
this.dep = undefined;
this._dirty = true;
this.__v_isref = true;
this[_a] = true;
let comparetarget;
let hascomparetarget = false;
let scheduled = false;
this.effect = new reactiveeffect(getter, (computedtrigger) => {
if (this.dep) {
if (computedtrigger) {
comparetarget = this._value;
hascomparetarget = true;
}
else if (!scheduled) {
const valuetocompare = hascomparetarget ? comparetarget : this._value;
scheduled = true;
hascomparetarget = false;
scheduler(() => {
if (this.effect.active && this._get() !== valuetocompare) {
triggerrefvalue(this);
}
scheduled = false;
});
}
for (const e of this.dep) {
if (e.computed instanceof deferredcomputedrefimpl) {
e.scheduler(true);
}
}
}
this._dirty = true;
});
this.effect.computed = this;
}
_get() {
if (this._dirty) {
this._dirty = false;
return (this._value = this.effect.run());
}
return this._value;
}
get value() {
trackrefvalue(this);
return toraw(this)._get();
}
}
const tick = promise.resolve();
const queue = [];
let queued = false;
const scheduler = (fn) => {
queue.push(fn);
if (!queued) {
queued = true;
tick.then(flush);
}
};
const flush = () => {
for (let i = 0; i < queue.length; i++) {
queue[i]();
}
queue.length = 0;
queued = false;
};
最后总结:
好啦!恭喜你完成了整个reactivity的阅读,相信你收获颇丰。我们在第一部分手写了简单版的reactivity让大家能够迅速理解reactivity的核心实现便于大家能更快理解后面部分的源码;在第二部分我们详细讲解了如何对数组和对象进行响应式处理;然后在第三部分我们详细讲解了对于set map等es6新出的结构进行拦截,与第二部分不同的是,集合类型的拦截是通过拦截各种操纵集合类型的api,然后实现的依赖收集和触发;最后一部分我们讲解了ref computed torefs的实现,然后贴出了一些不常用的api的源码。
以上就是vue3源码分析reactivity实现方法示例的详细内容,更多关于vue3源码分析reactivit方法的资料请关注代码网其它相关文章!
发表评论