reactive

简介: reactive

当比较refreactive时,需要注意到ref相比reactive在支持基本数据类型的响应性方面具有优势,但需要使用.value来访问属性。此外,使用reactive重新分配一个新对象会导致丢失响应性,而ref不会受到此影响。

那么,ref是如何实现比reactive更强大的功能呢?让我们深入Vue.js源码,一探究竟!

另外,让我们首先介绍一下与 ref 非常相似的函数 shallowRef,因为它们的实现方式非常相似,我们将它们放在一起讨论

这两个函数在代理基本数据类型时没有区别,但它们的区别在于如何代理复杂数据类型。下面的示例将帮助我们理解它们之间的区别。

        let obj = ref({
   
    foo: 1 });
        let obj1 = shallowRef({
   
    foo: 1 });
        effect(() => {
   
   
            console.log(obj.value.foo)
        })
        effect(() => {
   
   
            console.log(obj1.value.foo)
        })
        obj.value.foo = 2
        obj1.value.foo = 3
        //打印的结果是1,1,2

在上面的示例中,我们首先使用 ref 创建了一个对象 objshallowRef 创建了一个对象 obj1,它们都具有 foo 属性。然后,我们使用 effect 函数来监听这两个对象中的 foo 属性的变化。

当我们修改 objfoo 属性值时,第一个effect 中的回调会触发,并输出 2。这是因为 ref 创建的对象具有深层次的响应性,所以 foo 的变化被捕获了。

但当我们修改 obj1foo 属性值时,第二个effect 中的回调不会触发。这是因为 shallowRef 创建的对象没有建立深层次的响应关系,它只会捕获属性的直接变化,而不会递归地监视属性值的变化。

ok,接下来进入源码。(version:3.3.4)

function createRef(rawValue: unknown, shallow: boolean) {
   
   
  if (isRef(rawValue)) {
   
   
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

shallowRefref 函数都是由 createRef 这个工厂函数生成的。在 createRef 函数内部,首先会检查被代理对象是否已经是 ref 对象,如果是,则直接返回这个对象。否则,在工厂函数内部将创建一个 RefImpl 类的实例。

createRef 函数接受两个参数:rawValue 代表被代理对象,shallow 的作用是区分是要创建 shallowRef 还是 ref

RefImpl 类的构造函数将会接受这两个参数,这个类的构造函数将根据 shallow 参数的不同来设置代理对象的响应性方式,从而实现 refshallowRef 的不同行为。接下来进入构造函数

    constructor(value: T, public readonly __v_isShallow: boolean) {
   
   
        this.__v_isShallow = __v_isShallow
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value)
        this._value = __v_isShallow ? value : toReactive(value)
      }

      get value() {
   
   
      trackRefValue(this);
      return this._value;
    }
  1. __v_isShallow:区分ref,shallowRef
  2. __v_isRef:响应式对象是否由ref或shallowRef创建
  3. 如果是浅响应,实例上的_rawValue属性就是传入的value,但是如果是深响应,要考虑value是一个普通对象还是一个代理对象。
  4. toRaw的作用是如果value是一个普通对象,原样返回即可。如果已经是一个代理对象,那么它可以返回它的被代理对象,所以它返回的一定是一个原始对象.
  5. value是这个实例的访问器属性,当读取实例的value属性,触发get,需要收集依赖,并返回实例的_value属性

到了这里可以解决第一个疑问,即ref怎么实现对复杂数据类型的代理

访问value属性将会返回实例上的_value,当__v_isShallow为false,会返回后面的toReactive(value)

toReactive这个函数的作用是对value进行代理,如果value是简单数据类型,直接返回value.反之则会继续调用reactive函数对value进行代理,此时它的返回值是reactive生成的代理对象

结论:所以说ref还是调用了reactive来完成对复杂数据类型的代理。

    set value(newVal) {
   
   
      const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
      newVal = useDirectValue ? newVal : toRaw(newVal);
      if (hasChanged(newVal, this._rawValue)) {
   
   
        this._rawValue = newVal;
        this._value = useDirectValue ? newVal : toReactive(newVal);
        triggerRefValue(this, newVal);
      }
    }

对于设置set来说,isShallow(newVal) || isReadonly(newVal);不用管,因为它只是为了处理边界情况,纠结于这些细枝末节会十分痛苦。。。。面试也不会问,哈哈。

image.png

__v_isShallowtrue 时,newValue 无需额外处理。反之,需要考虑 newValue 可能是一个代理对象,因此必须将 newValue 设置为这个代理对象的原始值。

下面的 if 语句用于判断新旧值是否相同,如果它们相同,就无需触发通知。hasChanged 函数使用 Object.is 来进行比较,相比于 === 运算符,Object.isNaN的判断是true。接下来,将新值赋给 _rawValue,然后将经代理处理后的 newValue 赋值给 _value,并派发通知

这也可以解释第二个问题:给ref 重新分配一个普通对象不会导致失去响应性。这是因为新分配的值会经过 toReactive 处理,然后再赋给 _value,而 get 方法返回的就是 _value,也就是这个值已经经过响应式处理的数据。

可以发现一个规律:

使用shallowRef,即__v_isShallow为true时.在构造函数中不需要判断传人的值是不是原始对象还是代理对象.直接赋给_rawValue和_value.

而当__v_isShallow为false时,如果传入的是代理对象,将找到对应的原始对象赋值给_rawValue.被reactive处理之后的具有响应式的值将赋给_value

分离 _rawValue_value 使得在 ref 的内部逻辑中能够明确区分原始值和经过响应式处理的值,

考虑一种情况,我们将一个 ref 传入一个 reactive 代理对象,然后尝试设置新值,而这个新值刚好是与代理对象的原始值相同。此时,是否还需要触发通知(trigger)呢?

答案是否定的。即使新值与代理对象的原始值相同,Vue 3 仍会将其转化为代理对象并赋值给 _value,因此它们仍然引用相同的代理对象,这就意味着不需要触发通知。

然而,需要注意的是,如果是将一个 shallowRef 传入一个 reactive 代理对象,然后将这个代理对象对应的原始值设置给 shallowRef,这将会触发通知,因为 shallowRef 是浅引用,它只关注对象自身的变化而不深入追踪对象内部属性的变化。

最后可以总结一下,对于 refvalue 属性的读取和修改,并不是通过 Proxy 拦截来实现的,而是通过实例内部的属性访问器来完成的。

相关文章
|
移动开发 API PHP
分享101个PHP源码,总有一款适合您
分享101个PHP源码,总有一款适合您
282 1
|
SQL Oracle 关系型数据库
Oracle查询优化-处理空值
【1月更文挑战第1天】【1月更文挑战第1篇】数据库中的空值(例如:NULL或者 '')会导致一些不可预知的错误,因此需要在查询时就要处理好,确保数据查询的准确性,减轻代码压力。
463 0
|
小程序 JavaScript 数据库
微信小程序系列——上传下载图片以及图片的展示
微信小程序系列——上传下载图片以及图片的展示
|
7月前
|
机器学习/深度学习 人工智能 供应链
让AI真正懂生产的工业智能体长啥样?一个评测告诉你答案
本文探讨了工业互联网平台与大模型技术融合的趋势,重点介绍卡奥斯COSMOPlat平台。该平台通过智能体应用构建,提升生产效率、优化流程并降低开发成本。文章还分析了工业大模型在知识引擎、智能应用开发等方面的应用价值,并通过案例展示其在设备管理、供应链优化中的成效。随着政策推动和市场需求增长,“人工智能+工业互联网”模式正加速产业升级,为工业企业提供新生产力工具,助力数字化转型与智能化发展。
371 5
让AI真正懂生产的工业智能体长啥样?一个评测告诉你答案
|
SQL 存储 分布式计算
Hive TextFile数据错行问题解决方案
【8月更文挑战第16天】
297 0
|
SQL 关系型数据库 MySQL
SQLAlchemy使用指南
**SQLAlchemy 指南**:Python SQL 工具包,提供数据库高级抽象。安装:`pip install sqlalchemy`,加上数据库驱动(如 MySQL: `pip install mysql-connector-python`)。基础使用包括:创建数据库连接、定义模型、创建表、添加/查询/更新/删除数据。高级功能涉及关系映射、原生 SQL 语句及 SQLAlchemy Core。推荐阅读官方文档以深入了解。
991 1
|
算法 定位技术
插值、平稳假设、本征假设、变异函数、基台、块金、克里格、线性无偏最优…地学计算概念及公式推导
插值、平稳假设、本征假设、变异函数、基台、块金、克里格、线性无偏最优…地学计算概念及公式推导
383 2
|
存储 Shell 程序员
使用 Python 和 Pygame 制作游戏:第一章到第五章
使用 Python 和 Pygame 制作游戏:第一章到第五章
617 0
SSB配置异常引起的问题
这篇是两个SSB配置异常导致的问题总结,第一个问题很简单,但是由于第一次看到这种log,看起来也比较蒙,另外也是没想到还能有这么弱鸡的问题;之后又遇到了另外一个SSB相关的问题,因为涉及时频域资源的确定,看起来相对来说就比较费劲,这两个都是lab问题。
|
资源调度 JavaScript Java
服务器支持访问静态文件的几种方式:Spring、Tomcat、Vue
服务器支持访问静态文件的几种方式:Spring、Tomcat、Vue