当前位置: 首页 > news >正文

【vue3】响应式的几个重要API

创建响应式对象

RefImpl方式

   ref(data)

      使用这个构造函数RefImpl创建的响应式对象有一个属性:  __v_isRef=true。如果data是原始类型属性_value=_rawValue,就是原始值。如果data是对象类型,那么就使用toReactive-->reactive(data) ,此时_value就是一个响应式对象。不会重复封装被ref()封装过的值,

    function ref(value) {
      return createRef(value, false);
    }
    //首先判断是不是Ref,如果是就直接返回,就是不做嵌套封装
    function createRef(rawValue, shallow) {
      if (isRef(rawValue)) {
        return rawValue;
      }
      return new RefImpl(rawValue, shallow);
    }
    //浅层代理,就是第一层属性
    function shallowRef(value) {
      return createRef(value, true);
    }
 /// ref构造函数
    class RefImpl {
      ///__v_isShallow 是否是浅层代理,就是代理第一层
      constructor(value, __v_isShallow) {
        //__v_isShallow: 标记是否为浅层代理(仅代理第一层属性)
        this.__v_isShallow = __v_isShallow;
        this.dep = void 0;
        //判断是否是RefImpl对象的标识,如果是的话,那么就直接返回value
        this.__v_isRef = true;
        ///_rawValue存放的时最原始的值,就是去掉了响应式代理的值,toRaw会递归去掉代理
        this._rawValue = __v_isShallow ? value : toRaw(value);
        //如果toReactive()方法判断如果value是一个object 那么就用reactive() 包装 否则返回原值
        //toReactive对于原始值 就直接返回value
        //如果是对象的话 就是Proxy代理了
        this._value = __v_isShallow ? value : toReactive(value);
      }
      get value() {
        ///收集依赖 ReactiveEffect 副作用对象
        trackRefValue(this);
        return this._value;
      }
      set value(newVal) {
        // 判断是否直接使用原始值(不进行深度处理)
        const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
        newVal = useDirectValue ? newVal : toRaw(newVal);
        //判断新值和老值有没有变化,都是使用最原始的值来进行比较
        if (hasChanged(newVal, this._rawValue)) {
          const oldVal = this._rawValue;
          this._rawValue = newVal;
          this._value = useDirectValue ? newVal : toReactive(newVal);
          triggerRefValue(this, 5, newVal, oldVal);
        }
      }
    }
 const toReactive = (value) => isObject(value) ? reactive(value) : value;

reactive(data)方式

     如果data是原始类型的数据,就直接返回了原始值,这个方式是不能创建基于原始类型数据的响应式对象的,原始类型的数据 需要通过RefImpl 类来创建 。不会重复封装被reactive()封装过的值

    function reactive(target) {
      //判断是否只读
      if (isReadonly(target)) {
        return target;
      }
      return createReactiveObject(
        target,
        false,
        mutableHandlers,//这个handler是下面的BaseReactiveHandler 
        mutableCollectionHandlers,
        reactiveMap
      );
    }
 //target这个方法并没有判断 当前target是否是RefImpl类型
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
      if (!isObject(target)) {
        {
          warn$2(
            `value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String(
            target
          )}`
          );
        }
        return target;
      }
      ///如果本身是响应式数据 就有__v_raw __v_isReactive两个属性
      if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
        return target;
      }
      //proxyMap全局缓存,响应式数据
      const existingProxy = proxyMap.get(target);
      if (existingProxy) {
        return existingProxy;
      }
      ///返回target 的类型 Object.prototype.toString.call()
      const targetType = getTargetType(target);
      if (targetType === 0 /* INVALID */) {
        return target;
      }
      //如果是集合类型就用collectionHandlers处理,其他的用baseHandlers处理
      const proxy = new Proxy(
        target,
        targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
      );
      proxyMap.set(target, proxy);
      return proxy;
    }
class BaseReactiveHandler {
      constructor(_isReadonly = false, _isShallow = false) {
        this._isReadonly = _isReadonly;
        this._isShallow = _isShallow;
      }
      get(target, key, receiver) {
        const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
        ///通过get 方法设置了几个属性的陷阱 __v_isReactive __v_isReadonly __v_isShallow
        //__v_raw 这个属性是返回最原始的值
        if (key === "__v_isReactive") {
          return !isReadonly2;
        } else if (key === "__v_isReadonly") {
          return isReadonly2;
        } else if (key === "__v_isShallow") {
          return isShallow2;
        } else if (key === "__v_raw") {
          if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype
          // this means the reciever is a user proxy of the reactive proxy
          Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {
            return target;
          }
          return;
        }
        const targetIsArray = isArray(target);
        if (!isReadonly2) {
          if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
            return Reflect.get(arrayInstrumentations, key, receiver);
          }
          if (key === "hasOwnProperty") {
            return hasOwnProperty;
          }
        }
        const res = Reflect.get(target, key, receiver);
        if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
          return res;
        }
        if (!isReadonly2) {
          track(target, "get", key);
        }
        if (isShallow2) {
          return res;
        }
        if (isRef(res)) {
          return targetIsArray && isIntegerKey(key) ? res : res.value;
        }
        if (isObject(res)) {
          return isReadonly2 ? readonly(res) : reactive(res);
        }
        return res;
      }
    }

使用reactive() 方法创建的对象有一个这个属性 __v_reactive

Proxy代理对象创建

 class BaseReactiveHandler {
      constructor(_isReadonly = false, _isShallow = false) {
        this._isReadonly = _isReadonly;
        this._isShallow = _isShallow;
      }
      get(target, key, receiver) {
        const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
        ///__v_isReactive  陷阱属性
        if (key === "__v_isReactive") {
          return !isReadonly2;
        } else if (key === "__v_isReadonly") {
          return isReadonly2;
        } else if (key === "__v_isShallow") {
          return isShallow2;
        } else if (key === "__v_raw") {
          ///proxy对象通过get 设置了__v_raw 陷阱属性,通过这个属性可以判断是否是Proxy对象 
          if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype
          // this means the reciever is a user proxy of the reactive proxy
          Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {
            return target;
          }
          return;
        }
        //省略了部分代码
       }
       
}

响应式工具API

isRef

 作用

    判断当前对象是不是响应式(RefImpl)对象,

    function isRef(r) {
      //使用RefImpl封装的对象 都有这个属性__v_isRef,且为true. 不管r是原始值还是对象类型

      return !!(r && r.__v_isRef === true);

    }

unref

作用

  去掉响应式封装,不会递归

    function unref(ref2) {

      ///isRef函数根据__v_isRef属性来判断是否是有RefImpl的封装。如果是的话 就返回value值。这个value只也可能还是reactive()响应式对象。

         return isRef(ref2) ? ref2.value : ref2;

    }

isReactive

    作用判断value是否是一个reactive()创建的一个响应式对象

    function isReactive(value) {

      if (isReadonly(value)) {

        return isReactive(value["__v_raw"]); //__v_raw属性是原始对象值

      }

      //__v_isReactive这个属性是在reactive()创建是通过get方法设置的一个陷阱属性。

      return !!(value && value["__v_isReactive"]);

    }

isProxy

作用

    function isProxy(value) {

      return value ? !!value["__v_raw"] : false;

    }

isReadonly

  作用

   判断一个对象是否是只读的,有__v_isReadonly属性就是,只读具体什么概念??: 

    function isReadonly(value) {

      return !!(value && value["__v_isReadonly"]);

    }

toRaw

   作用就是把一个reactive创建的响应式对象递归返回最原始的值,如果是RefImpl创建的对象响应就不能解封了。如果是RefImpl可以通过toRaw(reactiveObj._value)来获取最原始值。

所以toRaw只对reactive创建的响应式对象才有用。那么RefImpl怎么解封呢?

    function toRaw(observed) {

       //__v_raw这个属性是使用reactive创建时通过get 方法设置的一个陷阱属性,具体实现

      //在: BaseReactiveHandler中

      const raw = observed && observed["__v_raw"];

      return raw ? toRaw(raw) : observed;

    }

proxyRefs   

function proxyRefs(objectWithRefs) {
      //如果objectWithRefs本身是reactive封装响应的式对象,就直接返回,
      //如果不是响应式 就用Proxy代理一次,但是这里创建的proxy对应的shallowUnwrapHandlers不会加入响应式功能
      return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);

    }
    //handler如下shallowUnwrapHandlers
    const shallowUnwrapHandlers = {
      get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),

      set: (target, key, value, receiver) => {
        const oldValue = target[key];
        if (isRef(oldValue) && !isRef(value)) {
          oldValue.value = value;
          return true;
        } else {
          return Reflect.set(target, key, value, receiver);
        }
      }
    };
//以下是组件初始化的部分代码
//用shallowUnwrapHandlers 拦截器 把setup结果封装成了代理Proxy但是没有响应式功能
instance.setupState = proxyRefs(setupResult);

{
 ///把结果setupState暴露到代理对象中去 
 exposeSetupStateOnRenderContext(instance);
}

function exposeSetupStateOnRenderContext(instance) {
   const { ctx, setupState } = instance;
   Object.keys(toRaw(setupState)).forEach((key) => {
        if (!setupState.__isScriptSetup) {
          if (isReservedPrefix(key[0])) {
            warn$1(
              `setup() return property ${JSON.stringify(
              key
            )} should not start with "$" or "_" which are reserved prefixes for Vue internals.`
            );
            return;
          }
          Object.defineProperty(ctx, key, {
            enumerable: true,
            configurable: true,
            get: () => setupState[key],
            set: NOOP
          });
        }
      });
    }

组件渲染时处理data和setupState

setupState是setup函数返回的结果。

在vue3内部api中的applyOptions方法中会处理data值。这个方式applyOptions是为了兼容vue2的选项式api的

 把instance.ctx上的属性代理到instance.proxy上。

 PublicInstanceProxyHandlers 的代码如下。可以从代码上看到页面上渲染时,如{{msg}},获取msg值的优先级别是:

SETUP函数返回结果 >data>ctx>props>publicPropertiesMap(全局的)>appContext.config.globalProperties
 

 const PublicInstanceProxyHandlers = {
      get({ _: instance }, key) {
        if (key === "__v_skip") {
          return true;
        }
        const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
        if (key === "__isVue") {
          return true;
        }
        let normalizedProps;
        if (key[0] !== "$") {
          //先从访问缓存中获取
          const n = accessCache[key];
          if (n !== void 0) {
            switch (n) {
              case 1 /* SETUP */:
                return setupState[key];
              case 2 /* DATA */:
                return data[key];
              case 4 /* CONTEXT */:
                return ctx[key];
              case 3 /* PROPS */:
                return props[key];
            }
          } else if (hasSetupBinding(setupState, key)) {
            accessCache[key] = 1 /* SETUP */;
            return setupState[key];
          } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
            accessCache[key] = 2 /* DATA */;
            return data[key];
          } else if (
            // only cache other properties when instance has declared (thus stable)
            // props
            (normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key)
          ) {
            accessCache[key] = 3 /* PROPS */;
            return props[key];
          } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
            accessCache[key] = 4 /* CONTEXT */;
            return ctx[key];
          } else if (shouldCacheAccess) {
            accessCache[key] = 0 /* OTHER */;
          }
        }
        ///返回公共属性的getter
        const publicGetter = publicPropertiesMap[key];
        let cssModule, globalProperties;
        if (publicGetter) {
          if (key === "$attrs") {
            track(instance.attrs, "get", "");
            markAttrsAccessed();
          } else if (key === "$slots") {
            track(instance, "get", key);
          }
          return publicGetter(instance);
        } else if (
          // css module (injected by vue-loader)
          (cssModule = type.__cssModules) && (cssModule = cssModule[key])
        ) {
          return cssModule;
        } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
          accessCache[key] = 4 /* CONTEXT */;
          return ctx[key];
        } else if (
          // global properties
          globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)
        ) {
          {
            return globalProperties[key];
          }
        } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading
        // to infinite warning loop
        key.indexOf("__v") !== 0)) {
          if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
            warn$1(
              `Property ${JSON.stringify(
              key
            )} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.`
            );
          } else if (instance === currentRenderingInstance) {
            warn$1(
              `Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`
            );
          }
        }
      },
      set({ _: instance }, key, value) {
        const { data, setupState, ctx } = instance;
        if (hasSetupBinding(setupState, key)) {
          setupState[key] = value;
          return true;
        } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) {
          warn$1(`Cannot mutate <script setup> binding "${key}" from Options API.`);
          return false;
        } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
          data[key] = value;
          return true;
        } else if (hasOwn(instance.props, key)) {
          warn$1(`Attempting to mutate prop "${key}". Props are readonly.`);
          return false;
        }
        if (key[0] === "$" && key.slice(1) in instance) {
          warn$1(
            `Attempting to mutate public property "${key}". Properties starting with $ are reserved and readonly.`
          );
          return false;
        } else {
          if (key in instance.appContext.config.globalProperties) {
            Object.defineProperty(ctx, key, {
              enumerable: true,
              configurable: true,
              value
            });
          } else {
            ctx[key] = value;
          }
        }
        return true;
      },
      has({
        _: { data, setupState, accessCache, ctx, appContext, propsOptions }
      }, key) {
        let normalizedProps;
        return !!accessCache[key] || data !== EMPTY_OBJ && hasOwn(data, key) || hasSetupBinding(setupState, key) || (normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key) || hasOwn(ctx, key) || hasOwn(publicPropertiesMap, key) || hasOwn(appContext.config.globalProperties, key);
      },
      defineProperty(target, key, descriptor) {
        if (descriptor.get != null) {
          target._.accessCache[key] = 0;
        } else if (hasOwn(descriptor, "value")) {
          this.set(target, key, descriptor.value, null);
        }
        return Reflect.defineProperty(target, key, descriptor);
      }
    };

在vue3内部api中的handleSetupResult方法中会处理setup函数返回的结果setupResult值。

响应式对象处理实例

实例1

 var refObj=ref({num1:123,num2:456}); 

 var tmpObj=toRaw(refObj);

var reactiveObj=reactive(tmpObj);

结果1:   tmpObj==refObj //true

                reactiveObj==refObj //false

因为: refObj首先是一个RefImpl对象,然后被封装的值是一个对象类型的,所以{num1:123,num2:456}会被封装成一个reactive()对象。所以refObj.value是一个响应式对象(Proxy)。然后使用toRaw(refObj),进行解封,但是由于refObj是一个RefImpl类型的对象,不能使用toRaw()方法类解封,因为这个toRaw是通过__v_raw陷阱属性类判断的,refImpl类型的没有这陷阱属性__v_raw。所以oRaw(refObj)还是原样返回refObj。所以 tmpObj==refObj 。

var reactiveObj=reactive(tmpObj); 因为tmpObj是一个RefImpl类型的对象,而使用reactive创建响应式对象时是不会判断当前是RefImpl类型的,只有tmpObj是reactive类型的才不会再次封装。所以这时要回给tmpObj在包装一层。 reactiveObj==refObj 为false.

总结1:

    toRaw 和unref都是分别解封  reactive和RefImpl类型,获取原始值。toRaw递归获取最原始的值。

   ref(value)在封装value之前会判断当前是否是 RefImpl,如果是就直接返回,不会再封装。

   reactive(value) 在封装之前也会判断value之前是否是reactive封装过,如果封装过就直接返回,不会再封装

       

相关文章:

  • 一学就废|Python基础碎片,Pathlib模块
  • 一.AI大模型开发-初识机器学习
  • vue3.x 的provide 与 inject详细解读
  • 用C++实现点到三角形最小距离的计算
  • 基于springboot的超时代停车场管理平台(源码+文档)
  • LabVIEW与USB设备开发
  • 关于post和get的请求参数问题
  • [JVM篇]虚拟机性能监控、故障处理工具
  • TDengine 数据备份/还原工具 taosdump
  • C#开源大型商城系统之B2B2C+O2O一体化_OctShop
  • kubectl top输出与Linux free命令不一致原因?
  • React常用库
  • 【PyTorch】torch.optim介绍
  • MySQL
  • CCF-CSP第19次认证第一题——线性分类器【NA】
  • Deep seek学习日记1
  • 人工智能 - 主动视觉可能就是你所需要的:在双臂机器人操作中探索主动视觉
  • 学习笔记之debian的thonny开发(尚未验证)--从stm32裸机到linux嵌入式系统
  • windows11+ubuntu20.04双系统下卸载ubuntu并重新安装
  • 人工智能 - 机器学习、深度学习、强化学习是人工智能领域的理论基础和方法论
  • 五大国货美妆去年业绩分化:珀莱雅百亿营收领跑,上海家化转亏
  • 4月译著联合书单|心爱之物:热爱如何联结并塑造我们
  • 李开复出任福耀科技大学理事会理事,助力学校AI战略
  • 秦洪看盘|上市公司业绩“排雷”近尾声,A股下行压力趋缓
  • 报告显示2024年全球军费开支增幅达冷战后最大
  • 专业竞演、剧场LIVE直播,32位越剧新星逐梦上海