首页前端开发VUEVue3源码分析reactivity实现方法示例

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

时间2024-02-11 05:41:03发布访客分类VUE浏览980
导读:收集整理的这篇文章主要介绍了Vue3源码分析reactivity实现方法示例,觉得挺不错的,现在分享给大家,也给大家做个参考。 目录深入分析对于map、set、weakMap、weakS...
收集整理的这篇文章主要介绍了Vue3源码分析reactivity实现方法示例,觉得挺不错的,现在分享给大家,也给大家做个参考。
目录
  • 深入分析对于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) {
      //如果被代理的是readonly返回已经被readonly代理过的target  if (isReadonly(target)) {
            return target;
      }
          return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers,    reactiveMap  );
    }
        
    • 之前我们分析了mutableHandlers、shallowReadonlyHandlers、readonlyHandlers、shallowReactiveHandlers,但是还有一个部分是没有分析的也就是对于集合类型的处理mutableCollectionHandlers、shallowReadonlyCollectionHandlers、readonlyCollectionHandlers、shallowCollectionHandlers下面我们看看这四个对象的庐山真面目吧!
    const mutableCollectionHandlers = {
      get: createinstrumentationGetter(false, false),}
        ;
    const shallowCollectionHandlers = {
      get: createInstrumentationGetter(false, true),}
        ;
    const readonlyCollectionHandlers = {
      get: createInstrumentationGetter(true, false),}
        ;
    const shallowReadonlyCollectionHandlers = {
      get: createInstrumentationGetter(true, true),}
        ;
        
    • 我们可以看到所有的collectionHandlers都是由工厂函数createInstrumentationGetter创建的,这里与之前的handlers不同,所有的拦截都只有一个方法了那就是get,这是因为对于map set等数据结构的操作与object和array的操作是不同的,对于set需要调用add,delete,has等方法map需要调用set,delete,has等方法所以不能直接对集合数据类型进行操作,那么我们就只需要拦截get获取到当前集合调用的方法然后对这个方法进行拦截就可以了。
    function createInstrumentationGetter(isReadonly, shallow) {
          const instrumentations = shallow    ? isReadonly      ? shallowReadonlyInstrumentations      : shallowInstrumentations    : isReadonly    ? readonlyInstrumentations    : mutableInstrumentations;
          return (target, key, receiver) =>
     {
        //对于map set的代理同样需要添加    if (key === IS_REACTIVE) {
              return !isReadonly;
        }
     else if (key === IS_READONLY) {
              return isReadonly;
        }
     else if (key === RAW) {
              return target;
        }
            //通过之前生成的拦截方法进行调度    return Reflect.get(      hasOwn(instrumentations, key) &
        &
         key in target        ? instrumentations        : target,      key,      receiver    );
      }
        ;
    }
        
    • 对于和之前相同的属性判断我们就不再赘述了,直接看mutableInstrumentations、readonlyInstrumentations、shallowInstrumentations、shallowReadonlyInstrumentations通过readonly和shallow的不同得到不同的处理器。那我们就需要看看这四个对象是如何生成的了。
    //通过拦截map set的方法实现代理export function createInstrumentations() {
      const mutableInstrumentations = {
      }
        ;
      const shallowInstrumentations = {
      }
        ;
      const readonlyInstrumentations = {
      }
        ;
      const shallowReadonlyInstrumentations = {
      }
        ;
          //其中keys,values,entries,Symbol.iterator是通过  //迭代器运行的,需要进行拦截  const iteratorMethods = ["keys", "values", "entries", Symbol.iterator];
          iteratorMethods.foreach((method) =>
     {
            mutableInstrumentations[method] = createIterableMethod(      method,      false,      false    );
            readonlyInstrumentations[method] = createIterableMethod(      method,      true,      false    );
            shallowInstrumentations[method] = createIterableMethod(method, false, true);
            shallowReadonlyInstrumentations[method] = createIterableMethod(      method,      true,      true    );
      }
        );
          return [    mutableInstrumentations,    readonlyInstrumentations,    shallowInstrumentations,    shallowReadonlyInstrumentations,  ];
    }
        

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

    (1).mutableInstrumentations

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

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

    //代理map set weakMap weakSet的get方法function get(target, key, isReadonly = false, isShallow = false) {
          target = target[RAW];
          //因为map的key可以是对象,所以需要rawKey  //同时收集依赖必须要rawTarget  const rawTarget = toRaw(target);
          const rawKey = toRaw(key);
      if (!isReadonly) {
        /**     * 为了实现在effect函数中无论是使用了以ProxyKey     * 还是以rawKey为键进行收集的依赖,在effect外部     * 修改PRoxyMap的proxyKey或rawKey都能触发依赖     * 更新,当使用proxyKey为键时,需要进行两次track     * 例如:当前在effect中获取的是proxyKey那么进行     * 两次track,在depsMap中就会有两个entries,分别     * 是以rawKey和proxyKey指向的deps但是指向的deps     * 不改变 那么在set中修改值的时候,无论是修改的     * proxyKey还是rawKey都能在depsMap中找到正确的     * 依赖进行更新     */    if (key !== rawKey) {
              track(rawTarget, trackOpTyPEs.get, key);
        }
            track(rawTarget, trackOpTypes.get, rawKey);
      }
      const {
     has }
         = getProto(rawTarget);
          const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
      //无论是使用rawKey还是key都能读取到  if (has.call(rawTarget, key)) {
            //仅需进行代理,并且返回代理后的对象    return wrap(target.get(key));
      }
     else if (has.call(rawTarget, rawKey)) {
            return wrap(target.get(rawKey));
      }
     else if (target !== rawTarget) {
            target.get(key);
      }
    }
        
    • 我们可以发现依赖收集触发了两次,当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 也可能是proxyKeyfunction 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的类型的处理:
    //clearif (type === triggerOpTypes.clear) {
          //清空,相当于所有的元素都发生改变  //故而全部都需要添加进依赖  deps = [...depsMap.values()];
    }
    //deletecase 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 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是否是reffunction 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" /* TriggerOpTypes.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;
    }
    //将代理的值变为reffunction 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方法的资料请关注其它相关文章!

    您可能感兴趣的文章:
    • React Immutable使用方法详细介绍
    • react中使用antd及immutable示例详解
    • react+ant design实现Table的增、删、改的示例代码
    • React 数据获取条件竞争原理解析
    • vue3源码分析reactivity实现原理
    • React实现基于Antd密码强度校验组件示例详解
    • React.memo React.useMemo对项目性能优化使用详解
    • react Table准备Spin Empty ConfigProvider组件实现

    声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


    若转载请注明出处: Vue3源码分析reactivity实现方法示例
    本文地址: https://pptw.com/jishu/609392.html
    vue3源码分析reactivity实现原理 TypeScript踩坑之TS7053的问题及解决

    游客 回复需填写必要信息