目录
- 深入分析对于map、set、weakMap、weakSet的响应式拦截
- (1).mutableInstrumentations
- (2).shallowInstrumentations
- (3).readonlyInstrumentations
- (4).shallowReadonlyInstrumentations
- ref、computed等方法的实现
- (1).ref与shallowRef源码解析
- (2).toRefs
- (4).computed
- (5)其他api源码
- 最后总结:
深入分析对于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) { |
| |
| 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) => { |
| |
| 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的不同得到不同的处理器。那我们就需要看看这四个对象是如何生成的了。
| |
| export function createInstrumentations() { |
| const mutableInstrumentations = { |
| }; |
| const shallowInstrumentations = { |
| }; |
| const readonlyInstrumentations = { |
| }; |
| const shallowReadonlyInstrumentations = { |
| }; |
| |
| |
| 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方法。
| |
| function get(target, key, isReadonly = false, isShallow = false) { |
| target = target[RAW]; |
| |
| |
| const rawTarget = toRaw(target); |
| const rawKey = toRaw(key); |
| if (!isReadonly) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (key !== rawKey) { |
| track(rawTarget, trackOpTypes.get, key); |
| } |
| track(rawTarget, trackOpTypes.get, rawKey); |
| } |
| const { has } = getProto(rawTarget); |
| const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive; |
| |
| 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方法:
| |
| 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方法:
| |
| function has(key, isReadonly = false) { |
| const target = this[RAW]; |
| const rawTarget = toRaw(target); |
| const rawKey = toRaw(key); |
| if (!isReadonly) { |
| |
| if (key !== rawKey) { |
| |
| 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的分析:
| |
| function add(value) { |
| value = toRaw(value); |
| const target = toRaw(this); |
| const proto = getProto(target); |
| |
| 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)) { |
| |
| deps.push(depsMap.get(ITERATE_KEY)); |
| if (isMap(target)) { |
| deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)); |
| } |
| } else if (isIntegerKey(key)) { |
| |
| |
| |
| |
| deps.push(depsMap.get("length")); |
| } |
| break; |
- 触发关于迭代器的依赖,例如在effect中执行了Object.keys map.entries map.keys等方法,那么ITERATE_KEY、MAP_KEY_ITERATE_KEY就会收集到相应的依赖函数。 继续set的分析:
| |
| function set(key, value) { |
| value = toRaw(value); |
| const target = toRaw(this); |
| const { has, get } = getProto(target); |
| |
| let hadKey = has.call(target, key); |
| |
| if (!hadKey) { |
| key = toRaw(key); |
| hadKey = has.call(target, key); |
| } else { |
| checkIdentityKeys(target, has, key); |
| } |
| |
| const oldValue = get.call(target, key); |
| |
| target.set(key, value); |
| |
| 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); |
| |
| |
| |
| |
| 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 !==; |
| |
| |
| 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的类型的处理:
| |
| if (type === triggerOpTypes.clear) { |
| |
| |
| deps = [...depsMap.values()]; |
| } |
| |
| 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" ]; |
| const rawTarget = toRaw(target); |
| const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive; |
| !isReadonly && |
| track(rawTarget, "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$(this, key, true); |
| }, |
| get size() { |
| return size(this, true); |
| }, |
| has(key) { |
| return has.call(this, key, true); |
| }, |
| |
| 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[] ? `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); |
| }, |
| |
| 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); |
| |
| 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; |
| |
| !isReadonly && |
| |
| track( |
| rawTarget, |
| trackOpTypes.iterate, |
| |
| isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY |
| ); |
| return { |
| |
| next() { |
| const { value, done } = innerIterator.next(); |
| return done |
| ? { value, done } |
| : { |
| |
| value: isPair ? [wrap(value[]), 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的源码实现:
| |
| function isRef(r) { |
| |
| return !!(r && r.__v_isRef === true); |
| } |
| function ref(value) { |
| |
| return createRef(value, false); |
| } |
| function shallowRef(value) { |
| |
| return createRef(value, true); |
| } |
| |
| function createRef(rawValue, shallow) { |
| if (isRef(rawValue)) { |
| return rawValue; |
| } |
| return new RefImpl(rawValue, shallow); |
| } |
这一段代码非常简单,就是通过工厂函数 createRef(value,isShallow) 传递当前需要代理的基本数据类型以及是否只需要代理第一层。我们接着向下分析,看看RefImpl实现吧!。
| class RefImpl { |
| constructor(value, __v_isShallow) { |
| |
| this.__v_isShallow = __v_isShallow; |
| |
| |
| this.dep = undefined; |
| this.__v_isRef = true; |
| |
| this._rawValue = __v_isShallow ? value : toRaw(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); |
| |
| 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) { |
| |
| if (shouldTrack && activeEffect) { |
| ref = toRaw(ref); |
| |
| trackEffects(ref.dep || (ref.dep = createDep()), { |
| target: ref, |
| type: "get", |
| key: 'value' |
| }); |
| } |
| } |
| function triggerRefValue(ref, newVal) { |
| ref = toRaw(ref); |
| if (ref.dep) { |
| |
| triggerEffects(ref.dep, { |
| target: ref, |
| type: "set" , |
| key: 'value', |
| newValue: newVal |
| }); |
| } |
| } |
- 我们可以发现整个ref的设计相当的简单,就是把需要代理的基本数据类型变为一个对象,然后再代理key为value值。
(2).toRefs
这是为了解决解构之后的proxy失去代理作用的api,例如:
| const proxy = reactive({a:,b:2}) |
| const {a,b} = proxy |
这样就失效了,但是如果你代理的是两层解构是不会出现proxy失效的,例如:
| const proxy = reactive({a:{a:},b:{b:1}}) |
| const {a,b} = proxy |
好了,为了解决第一种情况,toRefs出来了。
| 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) { |
| toRef返回ObjectRefImpl实例返回一个对象 |
| ret[key] = toRef(object, key); |
| } |
| return ret; |
| } |
| |
| function toRef(object, key, defaultValue) { |
| const val = object[key]; |
| return isRef(val) |
| ? val |
| : new ObjectRefImpl(object, key, defaultValue); |
| } |
| |
| |
| |
| |
| class ObjectRefImpl { |
| constructor(_object, _key, _defaultValue) { |
| |
| this._object = _object; |
| |
| this._key = _key; |
| this._defaultValue = _defaultValue; |
| this.__v_isRef = true; |
| } |
| get value() { |
| |
| |
| const val = this._object[this._key]; |
| return val === undefined ? this._defaultValue : val; |
| } |
| set value(newVal) { |
| |
| this._object[this._key] = newVal; |
| } |
| } |
toRefs就是在解构之前,把要访问的值变成一个对象,也就是说 {a} = toRefs(proxy) 中的a就是ObjectRefImpl实例,那么访问 .value 就会去访问 proxy[key] 这样就可以收集依赖,set的时候就会触发依赖。
(4).computed
这是一个计算属性的api,我们可以通过访问computed返回值的value属性获取最新的计算结果,并且computed返回值依然是响应式的,可以在effect中收集依赖,修改value属性的时候能触发依赖更新。
| |
| function computed(getterOrOptions, debugOptions, isSSR = false) { |
| let getter; |
| let setter; |
| |
| const onlyGetter = shared.isFunction(getterOrOptions); |
| if (onlyGetter) { |
| getter = getterOrOptions; |
| setter = () => { |
| console.warn('Write operation failed: computed value is readonly'); |
| }; |
| } |
| else { |
| |
| |
| |
| |
| 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; |
| |
| this.effect = new ReactiveEffect(getter, () => { |
| if (!this._dirty) { |
| this._dirty = true; |
| triggerRefValue(this); |
| } |
| }); |
| |
| 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的实现
| |
| 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 =; i < queue.length; i++) { |
| queue[i](); |
| } |
| queue.length =; |
| queued = false; |
| }; |
最后总结:
好啦!恭喜你完成了整个reactivity的阅读,相信你收获颇丰。我们在第一部分手写了简单版的reactivity让大家能够迅速理解reactivity的核心实现便于大家能更快理解后面部分的源码;在第二部分我们详细讲解了如何对数组和对象进行响应式处理;然后在第三部分我们详细讲解了对于set map等es6新出的结构进行拦截,与第二部分不同的是,集合类型的拦截是通过拦截各种操纵集合类型的api,然后实现的依赖收集和触发;最后一部分我们讲解了ref computed toRefs的实现,然后贴出了一些不常用的api的源码。