深入分析对于map、set、weakMap、weakSet的响应式拦截
在上篇的内容中我们以reactive为起点分析了 reactiv IT y 对于 array和object 的拦截,本文我们继续以 reactive 为起点分析 map、set、weakMap、weakSet 等数据结构的响应式拦截。
export function shallowReactive(t arg et) { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap ); } e xp ort 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: creat ei nstrumentationGetter(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.for each ((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函数中无论是使用了以 Proxy Key * 还是以rawKey为键进行收集的依赖,在effect外部 * 修改 PR oxyMap的proxyKey或rawKey都能触发依赖 * 更新,当使用proxyKey为键时,需要进行两次track * 例如:当前在effect中获取的是proxyKey那么进行 * 两次track,在depsMap中就会有两个entries,分别 * 是以rawKey和proxyKey指向的deps但是指向的deps * 不改变 那么在set中修改值的时候,无论是修改的 * proxyKey还是rawKey都能在depsMap中找到正确的 * 依赖进行更新 */ if (key !== rawKey) { track(rawTarget, trackOpTy PE s.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 & am p; & 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")); } br eak;触发关于迭代器的依赖,例如在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) : un define d; 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( `${sha red .capitalize( type )} opera tion ${key}f ai led: 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) .COM puted
这是一个计算属性的 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 = P rom ise.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方法的资料请关注其它相关 文章 !
您可能感兴趣的文章: React Immutable使用方法详细介绍 react中使用antd及immutable示例详解 react+ant design实现Table的增、删、改的示例代码 React 数据获取条件竞争原理解析 vue3源码分析reactivity实现原理 React实现基于Antd密码强度校验组件示例详解 React.memo React.useMemo对项目性能优化使用详解 react Table准备Spin Empty ConfigProvider组件实现
总结
以上是 为你收集整理的 Vue3源码分析reactivity实现方法示例 全部内容,希望文章能够帮你解决 Vue3源码分析reactivity实现方法示例 所遇到的问题。
如果觉得 网站内容还不错, 推荐好友。
查看更多关于Vue3源码分析reactivity实现方法示例的详细内容...