经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » React » 查看文章
Vue3源码分析reactivity实现方法示例
来源:jb51  时间:2023/1/20 8:39:28  对本文有异议

深入分析对于map、set、weakMap、weakSet的响应式拦截

在上篇的内容中我们以reactive为起点分析了reactivity对于array和object的拦截,本文我们继续以reactive为起点分析map、set、weakMap、weakSet等数据结构的响应式拦截。

  1. export function shallowReactive(target) {
  2. return createReactiveObject(
  3. target,
  4. false,
  5. shallowReactiveHandlers,
  6. shallowCollectionHandlers,
  7. shallowReactiveMap
  8. );
  9. }
  10. export function readonly(target) {
  11. return createReactiveObject(
  12. target,
  13. true,
  14. readonlyHandlers,
  15. readonlyCollectionHandlers,
  16. readonlyMap
  17. );
  18. }
  19. export function shallowReadonly(target) {
  20. return createReactiveObject(
  21. target,
  22. true,
  23. shallowReadonlyHandlers,
  24. shallowReadonlyCollectionHandlers,
  25. shallowReadonlyMap
  26. );
  27. }
  28. export function reactive(target) {
  29. //如果被代理的是readonly返回已经被readonly代理过的target
  30. if (isReadonly(target)) {
  31. return target;
  32. }
  33. return createReactiveObject(
  34. target,
  35. false,
  36. mutableHandlers,
  37. mutableCollectionHandlers,
  38. reactiveMap
  39. );
  40. }
  • 之前我们分析了mutableHandlers、shallowReadonlyHandlers、readonlyHandlers、shallowReactiveHandlers,但是还有一个部分是没有分析的也就是对于集合类型的处理mutableCollectionHandlers、shallowReadonlyCollectionHandlers、readonlyCollectionHandlers、shallowCollectionHandlers下面我们看看这四个对象的庐山真面目吧!
  1. const mutableCollectionHandlers = {
  2. get: createInstrumentationGetter(false, false),
  3. };
  4. const shallowCollectionHandlers = {
  5. get: createInstrumentationGetter(false, true),
  6. };
  7. const readonlyCollectionHandlers = {
  8. get: createInstrumentationGetter(true, false),
  9. };
  10. const shallowReadonlyCollectionHandlers = {
  11. get: createInstrumentationGetter(true, true),
  12. };
  • 我们可以看到所有的collectionHandlers都是由工厂函数createInstrumentationGetter创建的,这里与之前的handlers不同,所有的拦截都只有一个方法了那就是get,这是因为对于map set等数据结构的操作与object和array的操作是不同的,对于set需要调用add,delete,has等方法map需要调用set,delete,has等方法所以不能直接对集合数据类型进行操作,那么我们就只需要拦截get获取到当前集合调用的方法然后对这个方法进行拦截就可以了。
  1. function createInstrumentationGetter(isReadonly, shallow) {
  2. const instrumentations = shallow
  3. ? isReadonly
  4. ? shallowReadonlyInstrumentations
  5. : shallowInstrumentations
  6. : isReadonly
  7. ? readonlyInstrumentations
  8. : mutableInstrumentations;
  9. return (target, key, receiver) => {
  10. //对于map set的代理同样需要添加
  11. if (key === IS_REACTIVE) {
  12. return !isReadonly;
  13. } else if (key === IS_READONLY) {
  14. return isReadonly;
  15. } else if (key === RAW) {
  16. return target;
  17. }
  18. //通过之前生成的拦截方法进行调度
  19. return Reflect.get(
  20. hasOwn(instrumentations, key) && key in target
  21. ? instrumentations
  22. : target,
  23. key,
  24. receiver
  25. );
  26. };
  27. }
  • 对于和之前相同的属性判断我们就不再赘述了,直接看mutableInstrumentations、readonlyInstrumentations、shallowInstrumentations、shallowReadonlyInstrumentations通过readonly和shallow的不同得到不同的处理器。那我们就需要看看这四个对象是如何生成的了。
  1. //通过拦截map set的方法实现代理
  2. export function createInstrumentations() {
  3. const mutableInstrumentations = {
  4. };
  5. const shallowInstrumentations = {
  6. };
  7. const readonlyInstrumentations = {
  8. };
  9. const shallowReadonlyInstrumentations = {
  10. };
  11. //其中keys,values,entries,Symbol.iterator是通过
  12. //迭代器运行的,需要进行拦截
  13. const iteratorMethods = ["keys", "values", "entries", Symbol.iterator];
  14. iteratorMethods.forEach((method) => {
  15. mutableInstrumentations[method] = createIterableMethod(
  16. method,
  17. false,
  18. false
  19. );
  20. readonlyInstrumentations[method] = createIterableMethod(
  21. method,
  22. true,
  23. false
  24. );
  25. shallowInstrumentations[method] = createIterableMethod(method, false, true);
  26. shallowReadonlyInstrumentations[method] = createIterableMethod(
  27. method,
  28. true,
  29. true
  30. );
  31. });
  32. return [
  33. mutableInstrumentations,
  34. readonlyInstrumentations,
  35. shallowInstrumentations,
  36. shallowReadonlyInstrumentations,
  37. ];
  38. }

下面我们需要将内容分成四个部分,分别解读这四个对象的方法实现。

(1).mutableInstrumentations

  1. const mutableInstrumentations = {
  2. get(key) {
  3. return get(this, key);
  4. },
  5. get size() {
  6. return size(this);
  7. },
  8. has: has,
  9. add,
  10. set: set,
  11. delete: deleteEntry,
  12. clear,
  13. forEach: createForEach(false, false),
  14. };
  • 对于mutableInstrumentations的实现有get方法,这其实就是获取元素的方法,我们需要对这个方法进行拦截。
  • 简单的说,其实就是对set map的操作方法进行拦截,然后在获取值的时候进行收集依赖,在修改值的时候触发依赖核心依然没有改变。但是需要注意的是map的的key可以是对象,还有可能是代理对象,但是无论是对象还是代理对象我们都应该只能访问到唯一的那个值。

下面我们开始解读get方法。

  1. //代理map set weakMap weakSet的get方法
  2. function get(target, key, isReadonly = false, isShallow = false) {
  3. target = target[RAW];
  4. //因为map的key可以是对象,所以需要rawKey
  5. //同时收集依赖必须要rawTarget
  6. const rawTarget = toRaw(target);
  7. const rawKey = toRaw(key);
  8. if (!isReadonly) {
  9. /**
  10. * 为了实现在effect函数中无论是使用了以proxyKey
  11. * 还是以rawKey为键进行收集的依赖,在effect外部
  12. * 修改proxyMap的proxyKey或rawKey都能触发依赖
  13. * 更新,当使用proxyKey为键时,需要进行两次track
  14. * 例如:当前在effect中获取的是proxyKey那么进行
  15. * 两次track,在depsMap中就会有两个entries,分别
  16. * 是以rawKey和proxyKey指向的deps但是指向的deps
  17. * 不改变 那么在set中修改值的时候,无论是修改的
  18. * proxyKey还是rawKey都能在depsMap中找到正确的
  19. * 依赖进行更新
  20. */
  21. if (key !== rawKey) {
  22. track(rawTarget, trackOpTypes.get, key);
  23. }
  24. track(rawTarget, trackOpTypes.get, rawKey);
  25. }
  26. const { has } = getProto(rawTarget);
  27. const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
  28. //无论是使用rawKey还是key都能读取到
  29. if (has.call(rawTarget, key)) {
  30. //仅需进行代理,并且返回代理后的对象
  31. return wrap(target.get(key));
  32. } else if (has.call(rawTarget, rawKey)) {
  33. return wrap(target.get(rawKey));
  34. } else if (target !== rawTarget) {
  35. target.get(key);
  36. }
  37. }
  • 我们可以发现依赖收集触发了两次,当proxyKeykey的时候需要多触发一次依赖收集,这是为了保证后续无论是通过rawKey修改值还是通过proxyKey修改值最终都能触发到依赖。
  • 同样我们处在get当中,无论访问proxyKey还是rawKey我们都只能返回唯一的值。所以做了if elseif的判断。

接下来继续分析size方法:

  1. //对map set的size属性的拦截
  2. function size(target, isReadonly = false) {
  3. target = target[RAW];
  4. !isReadonly && track(toRaw(target), trackOpTypes.iterate, ITERATE_KEY);
  5. return Reflect.get(target, trackOpTypes.size, target);
  6. }
  • size属于属性的访问,所以肯定是进行track,这里的target都会调用toRaw,之前在proxy中传递给我们的对象本来就是代理前的对象所以不需要toRaw,但是当前我们是对方法进行的拦截所以this访问到的是代理后的对象所以需要对对象进行还原。
  • 这里就是对 "iterate" 进行了收集依赖,也就是说如果说执行set delete add clear都会触发这个依赖。具体可以看看后面对于这几个方法的实现。

下面继续分析has方法:

  1. //has进行依赖收集
  2. function has(key, isReadonly = false) {
  3. const target = this[RAW];//获取代理前的对象
  4. const rawTarget = toRaw(target);
  5. const rawKey = toRaw(key);//获取代理前的key
  6. if (!isReadonly) {
  7. //这里执行两次track的原因和上面相同
  8. if (key !== rawKey) {
  9. //收集依赖,类型为"has"
  10. track(rawTarget, trackOpTypes.has, key);
  11. }
  12. track(rawTarget, trackOpTypes.has, rawKey);
  13. }
  14. return key === rawKey
  15. ? target.has(key)
  16. : target.has(key) || target.has(rawKey);
  17. }
  • 其实这个type主要是传递上下文信息到onTrigger中(如果effect中有这个函数),所以本质都是通过target和key收集依赖。这个函数很简单就不在过多描述了。

继续add的分析:

  1. //对set的add方法的拦截
  2. function add(value) {
  3. value = toRaw(value); //获取rawValue
  4. const target = toRaw(this); //获取rawTarget
  5. const proto = getProto(target);
  6. //如果不存在这个值则是修改进行trigger
  7. const hadKey = proto.has.call(target, value);
  8. if (!hadKey) {
  9. target.add(value);
  10. trigger(target, triggerOpTypes.add, value, value);
  11. }
  12. return this;
  13. }

我们来看看对于 "add" 类型的trigger处理:

  1. case triggerOpTypes.add:
  2. if (!isArray(target)) {
  3. //map weakMap object
  4. deps.push(depsMap.get(ITERATE_KEY));
  5. if (isMap(target)) {
  6. deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
  7. }
  8. } else if (isIntegerKey(key)) {
  9. //当前修改的是数组且是新增值
  10. //例如 arr.length = 3 arr[4] = 8
  11. //此时数组长度会发生改变所以当前数组的
  12. //length属性依然需要被放入依赖
  13. deps.push(depsMap.get("length"));
  14. }
  15. break;
  • 触发关于迭代器的依赖,例如在effect中执行了Object.keys map.entries map.keys等方法,那么ITERATE_KEY、MAP_KEY_ITERATE_KEY就会收集到相应的依赖函数。 继续set的分析:
  1. //这里的key可能是rawKey 也可能是proxyKey
  2. function set(key, value) {
  3. value = toRaw(value); //获取原始的value值
  4. const target = toRaw(this); //获取原始的target
  5. const { has, get } = getProto(target);
  6. //判断当前使用的key能否获得值
  7. let hadKey = has.call(target, key);
  8. //获取不到可能是proxyKey,转化为rawKey再试试
  9. if (!hadKey) {
  10. key = toRaw(key);
  11. hadKey = has.call(target, key);
  12. } else {
  13. checkIdentityKeys(target, has, key);
  14. }
  15. //通过key获取
  16. const oldValue = get.call(target, key);
  17. //设置
  18. target.set(key, value);
  19. //rawKey和proxyKey都获取不到则是添加属性
  20. if (!hadKey) {
  21. //触发更新
  22. trigger(target, triggerOpTypes.add, key, value);
  23. }
  24. //修改属性
  25. else if (hasChanged(value, oldValue)) {
  26. trigger(target, triggerOpTypes.set, key, value, oldValue);
  27. }
  28. return this;
  29. }

object和array类似,但是依然需要处理proxyKey和rawKey的问题,如果proxyKey读取到了值则不使用rawKey如果读取不到转化为rawKey继续读取,然后根据hadKey判断是增加还是修改。

继续分析delete 和 clear:

  1. function deleteEntry(key) {
  2. const target = toRaw(this);
  3. const { has, get } = getProto(target);
  4. //删除的key可能是proxyKey也可能是rawKey
  5. //所以需要判断,判断的时候时候需要使用has
  6. //方法,所以需要对target还原,实际上所有的
  7. //操作都不能使用receiver,会造成二次依赖触发
  8. let hadKey = has.call(target, key);
  9. if (!hadKey) {
  10. key = toRaw(key);
  11. hadKey = has.call(target, key);
  12. } else {
  13. checkIdentityKeys(target, has, key);
  14. }
  15. const oldValue = get ? get.call(target, key) : undefined;
  16. const result = target.delete(key);
  17. //删除触发更新
  18. if (hadKey) {
  19. trigger(target, triggerOpTypes.delete, key, undefined, oldValue);
  20. }
  21. return result;
  22. }
  23. function clear() {
  24. const target = toRaw(this);
  25. const hadItems = target.size !== 0;
  26. //执行clear后 数据会被全部清空,oldTarget将不再存在
  27. //所以需要浅克隆保证旧数据依然能进入trigger
  28. const oldTarget = isMap(target) ? new Map(target) : new Set(target);
  29. const result = target.clear();
  30. if (hadItems) {
  31. trigger(target, triggerOpTypes.clear, undefined, undefined, oldTarget);
  32. }
  33. return result;
  34. }
  • delete和clear都是删除元素,所以是触发依赖,看看trigger对于delete和clear的类型的处理:
  1. //clear
  2. if (type === triggerOpTypes.clear) {
  3. //清空,相当于所有的元素都发生改变
  4. //故而全部都需要添加进依赖
  5. deps = [...depsMap.values()];
  6. }
  7. //delete
  8. case triggerOpTypes.delete:
  9. if (!isArray(target)) {
  10. deps.push(depsMap.get(ITERATE_KEY));
  11. if (isMap(target)) {
  12. deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
  13. }
  14. }
  15. break;
  • 对于clear因为所有元素都被删除了,所以所有元素的依赖都需要被触发。
  • 对于delete,则是触发执行了forEach、entries keys values等方法的依赖。当然删除元素本身的依赖同样需要被执行。

最后一个forEach:

  1. function createForEach(isReadonly, isShallow) {
  2. return function forEach(callback, thisArg) {
  3. const observed = this;
  4. const target = observed["__v_raw" /* ReactiveFlags.RAW */];
  5. const rawTarget = toRaw(target);
  6. const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
  7. !isReadonly &&
  8. track(rawTarget, "iterate" /* TrackOpTypes.ITERATE */, ITERATE_KEY);
  9. return target.forEach((value, key) => {
  10. return callback.call(thisArg, wrap(value), wrap(key), observed);
  11. });
  12. };
  13. }
  • 当调用了forEach函数 也就是Map.forEach或者Set.forEach,这个也是靠迭代器所以依赖的收集则是ITERATE_KEY。 好了,到目前为止所有的api都已经分析完成了。收集依赖的方法是get has size forEach entries keys values,触发依赖则是clear set delete addforEach、size、entries、keys、values方法会收集ITERATE_KEY或MAP_KEY_ITERATE_KEY的依赖。delete add set则会调用迭代器的依赖,换句话说就是集合的元素增加减少都会调用迭代器收集的依赖。

(2).shallowInstrumentations

  1. const shallowInstrumentations = {
  2. get(key) {
  3. return get(this, key, false, true);
  4. },
  5. get size() {
  6. return size(this);
  7. },
  8. has: has,
  9. add,
  10. set: set,
  11. delete: deleteEntry,
  12. clear,
  13. forEach: createForEach(false, true),
  14. };
  • 传递readonly、shallow生成不同的get和forEach。

(3).readonlyInstrumentations

  1. const readonlyInstrumentations = {
  2. get(key) {
  3. return get$1(this, key, true);
  4. },
  5. get size() {
  6. return size(this, true);
  7. },
  8. has(key) {
  9. return has.call(this, key, true);
  10. },
  11. //只读的属性是不需要修改的,全部通过warn提示
  12. add: createReadonlyMethod(triggerOpTypes.add),
  13. set: createReadonlyMethod(triggerOpTypes.set),
  14. delete: createReadonlyMethod(triggerOpTypes.delete),
  15. clear: createReadonlyMethod(triggerOpTypes.clear),
  16. forEach: createForEach(true, false),
  17. };
  18. function createReadonlyMethod(type) {
  19. return function (...args) {
  20. {
  21. const key = args[0] ? `on key "${args[0]}" ` : ``;
  22. console.warn(
  23. `${shared.capitalize(
  24. type
  25. )} operation ${key}failed: target is readonly.`,
  26. toRaw(this)
  27. );
  28. }
  29. return type === triggerOpTypes.delete ? false : this;
  30. };
  31. }
  • 对于readonly类型不能够修改所以只要访问set add delete clear等方法就会发出警告并且不能修改。

(4).shallowReadonlyInstrumentations

  1. const shallowReadonlyInstrumentations = {
  2. get(key) {
  3. return get(this, key, true, true);
  4. },
  5. get size() {
  6. return size(this, true);
  7. },
  8. has(key) {
  9. return has.call(this, key, true);
  10. },
  11. //只读的属性是不需要修改的,全部通过warn提示
  12. add: createReadonlyMethod(triggerOpTypes.add),
  13. set: createReadonlyMethod(triggerOpTypes.set),
  14. delete: createReadonlyMethod(triggerOpTypes.delete),
  15. clear: createReadonlyMethod(triggerOpTypes.clear),
  16. forEach: createForEach(true, true),
  17. };

与第三种情况相同。

当然对于entries values keys Symbol.iterator的拦截还没有分析,我们继续看看实现的源码:

  1. function createIterableMethod(method, isReadonly, isShallow) {
  2. return function (...args) {
  3. const target = this[RAW];
  4. const rawTarget = toRaw(target);
  5. const targetIsMap = isMap(rawTarget); //被代理对象是否是map
  6. //如果是entries方法,会返回key和value
  7. const isPair =
  8. method === "entries" || (method === Symbol.iterator && targetIsMap);
  9. const isKeyOnly = method === "keys" && targetIsMap;
  10. //调用这个方法,返回迭代器
  11. const innerIterator = target[method](...args);
  12. //获取当前需要代理的函数
  13. const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
  14. //readonly不需要track
  15. !isReadonly &&
  16. //追踪
  17. track(
  18. rawTarget,
  19. trackOpTypes.iterate,
  20. //如果是Map且访问的keys方法则是MAP_KEY_ITERATE_KEY
  21. isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
  22. );
  23. return {
  24. //重写迭代器方法 key,value还可以被深度代理
  25. next() {
  26. const { value, done } = innerIterator.next();
  27. return done
  28. ? { value, done }
  29. : {
  30. //如果是entries方法value则是key和value
  31. value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
  32. done,
  33. };
  34. },
  35. [Symbol.iterator]() {
  36. return this;
  37. },
  38. };
  39. };
  40. }

总结一下:对于map set weakMap weakSet的拦截,主要处理的有两个地方:

  • 第一:对于mapweakMap类型,他们的key可能是一个对象,那么对象就可能是被代理过的对象,但是无论通过proxyKey访问还是rawKey访问到的对象都是一样的,同样的在effect中使用proxyKey,那么会触发依赖收集,这个时候会存放进行两次track,保证在effect外部修改proxy值的时候,无论是使用proxyKey修改还是rawKey修改最后都能正确触发依赖。
  • 第二:当时用entries keys values forEach等集合方法的时候,收集依赖的key则是ITERATE_KEYMAP_KEY_ITERATE_KEY,当进行add delete set操作的时候会多添加在ITERATE_KEYMAP_KEY_ITERATE_KEY时收集到的依赖,保证了即使使用集合方法或者迭代器依然能够进行依赖收集和触发。
  • 第三:整个reactivity的核心依然没有改变,只是拦截变成了拦截操作数据的方法,依旧是访问的时候收集依赖,修改的时候触发依赖。

ref、computed等方法的实现

(1).ref与shallowRef源码解析

上面我们讲述了对于对象数组等数据的代理,但是如果是string、number等基本数据类型呢?我们就需要采用ref这个api来实现代理了。我们先来看看refshallowRef的源码实现:

  1. //判断当前r是否是ref
  2. function isRef(r) {
  3. //根本就是判断当前对象上是否有__v_isRef属性
  4. return !!(r && r.__v_isRef === true);
  5. }
  6. function ref(value) {
  7. //创建ref的工厂函数,第二个参数为是为为shallow
  8. return createRef(value, false);
  9. }
  10. function shallowRef(value) {
  11. //第二个参数为true表示当前是shallow
  12. return createRef(value, true);
  13. }
  14. //如果是ref则返回ref,只对非ref进行代理
  15. function createRef(rawValue, shallow) {
  16. if (isRef(rawValue)) {
  17. return rawValue;
  18. }
  19. return new RefImpl(rawValue, shallow);
  20. }

这一段代码非常简单,就是通过工厂函数 createRef(value,isShallow) 传递当前需要代理的基本数据类型以及是否只需要代理第一层。我们接着向下分析,看看RefImpl实现吧!。

  1. class RefImpl {
  2. constructor(value, __v_isShallow) {
  3. //是否由shallowRef创建
  4. this.__v_isShallow = __v_isShallow;
  5. //这个dep和target,key对应的dep是一个意思
  6. //可以理解为target = this;key="value"对应的dep
  7. this.dep = undefined;
  8. this.__v_isRef = true;//是否是ref
  9. //未代理的value
  10. this._rawValue = __v_isShallow ? value : toRaw(value);
  11. //代理过后的value
  12. this._value = __v_isShallow ? value : toReactive(value);
  13. }
  14. get value() {
  15. //收集所有的依赖
  16. trackRefValue(this);
  17. return this._value;
  18. }
  19. set value(newVal) {
  20. //是否还需要进行深度代理
  21. const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
  22. newVal = useDirectValue ? newVal : toRaw(newVal);
  23. //如果当前值发生了修改相当于Object.is
  24. if (shared.hasChanged(newVal, this._rawValue)) {
  25. this._rawValue = newVal;
  26. this._value = useDirectValue ? newVal : toReactive(newVal);
  27. //触发依赖更新
  28. triggerRefValue(this, newVal);
  29. }
  30. }
  31. }
  32. //两个工具函数
  33. const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;
  34. const toReadonly = (value) => shared.isObject(value) ? readonly(value) : value;
  • 我们可以发现这里的拦截只有getset了,当然也不需要deleteProperty has ownKeys的拦截了,所以我们通过类自带的拦截器进行拦截,同样的逻辑get的时候收集依赖,set的时候触发依赖。
  1. function trackRefValue(ref) {
  2. //判断当前activeEffect是否存在不存在则不需要收集依赖
  3. if (shouldTrack && activeEffect) {
  4. ref = toRaw(ref);
  5. //收集target为ref key为"value"的依赖
  6. trackEffects(ref.dep || (ref.dep = createDep()), {
  7. target: ref,//target相当于ref
  8. type: "get",//类型是"get"
  9. key: 'value'//key是"value"
  10. });
  11. }
  12. }
  13. function triggerRefValue(ref, newVal) {
  14. ref = toRaw(ref);
  15. if (ref.dep) {
  16. //触发target为ref key为"value"的依赖
  17. triggerEffects(ref.dep, {
  18. target: ref,
  19. type: "set" /* TriggerOpTypes.SET */,
  20. key: 'value',
  21. newValue: newVal
  22. });
  23. }
  24. }
  • 我们可以发现整个ref的设计相当的简单,就是把需要代理的基本数据类型变为一个对象,然后再代理keyvalue值。

(2).toRefs

这是为了解决解构之后的proxy失去代理作用的api,例如:

  1. const proxy = reactive({a:1,b:2})
  2. const {a,b} = proxy //失效

这样就失效了,但是如果你代理的是两层解构是不会出现proxy失效的,例如:

  1. const proxy = reactive({a:{a:1},b:{b:1}})
  2. const {a,b} = proxy //a,b依然是响应式的

好了,为了解决第一种情况,toRefs出来了。

  1. function toRefs(object) {
  2. //如果不是代理过的对象,不能使用toRefs
  3. if (!isProxy(object)) {
  4. console.warn(`toRefs() expects a reactive object but received a plain one.`);
  5. }
  6. //创建容器
  7. const ret = isArray(object) ? new Array(object.length) : {};
  8. //将解构后的值变为响应式赋值给ret容器
  9. for (const key in object) {
  10. toRef返回ObjectRefImpl实例返回一个对象
  11. ret[key] = toRef(object, key);
  12. }
  13. return ret;
  14. }
  15. //将代理的值变为ref
  16. function toRef(object, key, defaultValue) {
  17. const val = object[key];
  18. return isRef(val)
  19. ? val
  20. : new ObjectRefImpl(object, key, defaultValue);
  21. }
  22. //ObjectRefImpl实例访问value的时候相当于是
  23. //访问的proxy[key]这样就依旧是响应式的
  24. //同理设置的时候proxy[key] = xxx也是响应式的
  25. //我们只需要访问.value和设置.value就可以了
  26. class ObjectRefImpl {
  27. constructor(_object, _key, _defaultValue) {
  28. //存储proxy
  29. this._object = _object;
  30. //存储key
  31. this._key = _key;
  32. this._defaultValue = _defaultValue;
  33. this.__v_isRef = true;//当前是ref
  34. }
  35. get value() {
  36. //this._object[this._key]相当于读取了proxy中的值
  37. //会收集依赖
  38. const val = this._object[this._key];
  39. return val === undefined ? this._defaultValue : val;
  40. }
  41. set value(newVal) {
  42. //设置了proxy中的值触发依赖更新
  43. this._object[this._key] = newVal;
  44. }
  45. }

toRefs就是在解构之前,把要访问的值变成一个对象,也就是说 {a} = toRefs(proxy) 中的a就是ObjectRefImpl实例,那么访问 .value 就会去访问 proxy[key] 这样就可以收集依赖,set的时候就会触发依赖。

(4).computed

这是一个计算属性的api,我们可以通过访问computed返回值的value属性获取最新的计算结果,并且computed返回值依然是响应式的,可以在effect中收集依赖,修改value属性的时候能触发依赖更新。

  1. //对传递的参数进行整理生成ComputedRefImpl实例并返回
  2. function computed(getterOrOptions, debugOptions, isSSR = false) {
  3. let getter;
  4. let setter;
  5. //第一个参数是函数,则只有getter没有setter
  6. const onlyGetter = shared.isFunction(getterOrOptions);
  7. if (onlyGetter) {
  8. getter = getterOrOptions;
  9. setter = () => {
  10. console.warn('Write operation failed: computed value is readonly');
  11. };
  12. }
  13. else {
  14. //获取getter和setter
  15. //getter返回一个计算值
  16. //如果setter存在当修改ComputedRefImpl实例的value属性
  17. //的时候会调用setter并把修改的值传递到setter中
  18. getter = getterOrOptions.get;
  19. setter = getterOrOptions.set;
  20. }
  21. //创建实例
  22. const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
  23. if (debugOptions && !isSSR) {
  24. cRef.effect.onTrack = debugOptions.onTrack;
  25. cRef.effect.onTrigger = debugOptions.onTrigger;
  26. }
  27. return cRef;
  28. }
  • computed本身只是对传递的参数进行了整理,然后创建了ComputedRefImpl实例并且返回。
  1. _a = "__v_isReadonly"
  2. class ComputedRefImpl {
  3. constructor(getter, _setter, isReadonly, isSSR) {
  4. this._setter = _setter;
  5. this.dep = undefined;
  6. this.__v_isRef = true;
  7. this[_a] = false;
  8. this._dirty = true;
  9. //这里的逻辑reactivity上篇中已经讲过了
  10. this.effect = new ReactiveEffect(getter, () => {
  11. if (!this._dirty) {
  12. this._dirty = true;
  13. triggerRefValue(this);
  14. }
  15. });
  16. //在trigger中优先触发有computed属性的effect
  17. this.effect.computed = this;
  18. this.effect.active = this._cacheable = !isSSR;
  19. this["__v_isReadonly"] = isReadonly;
  20. }
  21. get value() {
  22. const self = toRaw(this);
  23. trackRefValue(self);
  24. if (self._dirty || !self._cacheable) {
  25. self._dirty = false;
  26. self._value = self.effect.run();
  27. }
  28. return self._value;
  29. }
  30. set value(newValue) {
  31. this._setter(newValue);
  32. }
  33. }

construtor中创建ReactiveEffect实例,第二个函数代表的是schduler调度器,如果有这个函数,那么触发依赖的时候将不会调用run方法而是调用schduler,所以如果调用这个函数表示computed中的getter中的某个代理属性发生了改变.然后 _dirty = true 表示值发生了改变,那么ComputedRefImpl收集到的依赖将会被触发,同样的ComputedRefImpl的依赖是在访问ComputedRefImplvalue属性的时候收集到的。

(5)其他api源码

最后还有customRef以及deferredComputed大家看看源码吧,不在进行讲解了。

1.customRef的实现

  1. //customRef的实现
  2. function customRef(factory) {
  3. return new CustomRefImpl(factory);
  4. }
  5. class CustomRefImpl {
  6. constructor(factory) {
  7. this.dep = undefined;
  8. this.__v_isRef = true;
  9. const { get, set } = factory(
  10. () => trackRefValue(this),
  11. () => triggerRefValue(this)
  12. );
  13. this._get = get;
  14. this._set = set;
  15. }
  16. get value() {
  17. return this._get();
  18. }
  19. set value(newVal) {
  20. this._set(newVal);
  21. }
  22. }

2.deferredComputed的实现

  1. function deferredComputed(getter) {
  2. return new DeferredComputedRefImpl(getter);
  3. }
  4. class DeferredComputedRefImpl {
  5. constructor(getter) {
  6. this.dep = undefined;
  7. this._dirty = true;
  8. this.__v_isRef = true;
  9. this[_a] = true;
  10. let compareTarget;
  11. let hasCompareTarget = false;
  12. let scheduled = false;
  13. this.effect = new ReactiveEffect(getter, (computedTrigger) => {
  14. if (this.dep) {
  15. if (computedTrigger) {
  16. compareTarget = this._value;
  17. hasCompareTarget = true;
  18. }
  19. else if (!scheduled) {
  20. const valueToCompare = hasCompareTarget ? compareTarget : this._value;
  21. scheduled = true;
  22. hasCompareTarget = false;
  23. scheduler(() => {
  24. if (this.effect.active && this._get() !== valueToCompare) {
  25. triggerRefValue(this);
  26. }
  27. scheduled = false;
  28. });
  29. }
  30. for (const e of this.dep) {
  31. if (e.computed instanceof DeferredComputedRefImpl) {
  32. e.scheduler(true);
  33. }
  34. }
  35. }
  36. this._dirty = true;
  37. });
  38. this.effect.computed = this;
  39. }
  40. _get() {
  41. if (this._dirty) {
  42. this._dirty = false;
  43. return (this._value = this.effect.run());
  44. }
  45. return this._value;
  46. }
  47. get value() {
  48. trackRefValue(this);
  49. return toRaw(this)._get();
  50. }
  51. }
  52. const tick = Promise.resolve();
  53. const queue = [];
  54. let queued = false;
  55. const scheduler = (fn) => {
  56. queue.push(fn);
  57. if (!queued) {
  58. queued = true;
  59. tick.then(flush);
  60. }
  61. };
  62. const flush = () => {
  63. for (let i = 0; i < queue.length; i++) {
  64. queue[i]();
  65. }
  66. queue.length = 0;
  67. queued = false;
  68. };

最后总结:

好啦!恭喜你完成了整个reactivity的阅读,相信你收获颇丰。我们在第一部分手写了简单版的reactivity让大家能够迅速理解reactivity的核心实现便于大家能更快理解后面部分的源码;在第二部分我们详细讲解了如何对数组和对象进行响应式处理;然后在第三部分我们详细讲解了对于set mapes6新出的结构进行拦截,与第二部分不同的是,集合类型的拦截是通过拦截各种操纵集合类型的api,然后实现的依赖收集和触发;最后一部分我们讲解了ref computed toRefs的实现,然后贴出了一些不常用的api的源码。

以上就是Vue3源码分析reactivity实现方法示例的详细内容,更多关于Vue3源码分析reactivit方法的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号