返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Vue3源码分析reactivity实现方法示例
  • 496
分享到

Vue3源码分析reactivity实现方法示例

Vue3源码分析reactivit方法Vue reactivit 2023-01-28 06:01:07 496人浏览 独家记忆
摘要

目录深入分析对于map、set、weakMap、weakSet的响应式拦截(1).mutableInstrumentations(2).shallowInstrumentations

深入分析对于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) {
    
    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);
  }
}
  • 我们可以发现依赖收集触发了两次,当proxyKeykey的时候需要多触发一次依赖收集,这是为了保证后续无论是通过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" ];
    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 addforEach、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的拦截,主要处理的有两个地方:

  • 第一:对于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的源码实现:

//判断当前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;
  • 我们可以发现这里的拦截只有getset了,当然也不需要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" ,
     key: 'value',
     newValue: newVal
   });     
 }
}
  • 我们可以发现整个ref的设计相当的简单,就是把需要代理的基本数据类型变为一个对象,然后再代理keyvalue值。

(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的依赖是在访问ComputedRefImplvalue属性的时候收集到的。

(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 mapes6新出的结构进行拦截,与第二部分不同的是,集合类型的拦截是通过拦截各种操纵集合类型的api,然后实现的依赖收集和触发;最后一部分我们讲解了ref computed toRefs的实现,然后贴出了一些不常用的api的源码。

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

--结束END--

本文标题: Vue3源码分析reactivity实现方法示例

本文链接: https://lsjlt.com/news/178152.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Vue3源码分析reactivity实现方法示例
    目录深入分析对于map、set、weakMap、weakSet的响应式拦截(1).mutableInstrumentations(2).shallowInstrumentations...
    99+
    2023-01-28
    Vue3源码分析reactivit方法 Vue reactivit
  • vue3源码分析reactivity实现原理
    目录引言第一部分:简单版reactivity(1).实现reactive和effect(2).实现ref(3).实现computed第二部分:深入分析对于object、array的响...
    99+
    2023-01-28
    vue3源码分析reactivity vue reactivity
  • vue3新方法源码分析
    这篇文章主要讲解了“vue3新方法源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue3新方法源码分析”吧!创建应用程序在 Vue 3 中,创建应用程序的方式有所改变。传统上,我们使...
    99+
    2023-07-06
  • Vue3 源码分析reactive readonly实例
    目录引言一、reactive 和 readonly1. reactive相关类型2. 相关全局变量与方法3. reactive函数4. 造物主createReactiveObject...
    99+
    2022-11-13
    Vue3 reactive readonly Vue3 reactive
  • vue3源码剖析之简单实现方法
    目录前言🍹准备工作🍲vue3用法🍖实现总结前言 最近,由于我的第一个vue3 + ts的正式项目,已经进入验收阶段。听你们老说vue3...
    99+
    2024-04-02
  • laravel源码分析队列Queue方法示例
    目录前言队列任务的创建队列任务的分发前言 队列 (Queue) 是 laravel 中比较常用的一个功能,队列的目的是将耗时的任务延时处理,比如发送邮件,从而大幅度缩短 Web 请求...
    99+
    2024-04-02
  • mysql源码示例分析
    这篇文章主要介绍了mysql源码示例分析,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。mysql的内存管理庞大而先进,这在mem0pool.c文...
    99+
    2024-04-02
  • Vue3源码解析watch函数实例
    目录引言一、watch参数类型1. 选项options2. 回调cb3. 数据源source二、watch函数三、watch的核心:doWatch 函数引言 想起上次面试,问了个古老...
    99+
    2022-11-13
    Vue3 watch函数 Vue watch
  • Vue.use源码的示例分析
    这篇文章将为大家详细讲解有关Vue.use源码的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。先上vue.use源码// javascript的方法是可...
    99+
    2024-04-02
  • python实例方法、静态方法与类方法源码分析
    本篇内容介绍了“python实例方法、静态方法与类方法源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成...
    99+
    2024-04-02
  • Spring源码解析之推断构造方法的示例分析
    小编给大家分享一下Spring源码解析之推断构造方法的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Spring推断构造方法贴个测试代码直接开干,这只是个...
    99+
    2023-06-15
  • Vue3开发实例代码分析
    获取 thisVue2 中每个组件里使用 this 都指向当前组件实例,this 上还包含了全局挂载的东西,都知道 this.xxx 啥都有而 Vue3 中没有 this,如果想要类似的用法有两种,一是获取当前组件实例,二是获取全局实例,如...
    99+
    2023-05-17
    Vue3
  • Vue3源码分析侦听器watch的实现原理
    目录watch 的本质watch 的函数签名侦听多个源侦听单一源watch 的实现watch 函数source 参数cb 参数options 参数doWatch 函数doWatch ...
    99+
    2022-11-13
    Vue3侦听器watch Vue3侦听器watch原理
  • PostgreSQL MVCC源码的示例分析
    这篇文章主要为大家展示了“PostgreSQL MVCC源码的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“PostgreSQL MVCC源码的示例分析...
    99+
    2024-04-02
  • Java源码ConcurrentHashMap的示例分析
    小编给大家分享一下Java源码ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、记录形式打算直接把过程写在源码中,会按序进行注释,查阅的时候可以按序号只看注释部分二、Concur...
    99+
    2023-06-15
  • AbstractStringBuilder类源码的示例分析
    这篇文章给大家分享的是有关AbstractStringBuilder类源码的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。因为看StringBuffer 和 StringBuilder 的源码时发现两者都...
    99+
    2023-05-30
  • ibatis源码与平台源码的示例分析
    这篇文章主要介绍了ibatis源码与平台源码的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 原生类加...
    99+
    2024-04-02
  • mysql5.6.8源码安装的示例分析
    小编给大家分享一下mysql5.6.8源码安装的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!内核:[root@opop...
    99+
    2024-04-02
  • MySQL中MHA源码的示例分析
    小编给大家分享一下MySQL中MHA源码的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧! MySQL的高可用方案很多,...
    99+
    2024-04-02
  • MySQL源码安装的示例分析
    这篇文章主要介绍MySQL源码安装的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完! 操作系统:CentOS 6.7MySQL版本:5.6.30 ·  ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作