jtwang7 / Vue-Note

Vue 学习笔记
0 stars 0 forks source link

Vue3 初探 ref 与 reactive 响应式原理 #15

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

Vue3 初探 ref 与 reactive 响应式原理

参考文章:一文搞懂Vue3的响应式函数ref和reactive

✅ 作用:声明响应式数据 Vue3中通过ref()reactive()函数来声明响应式数据。 ref()可以声明任意类型的响应式数据。reactive()只能声明对象(数组)类型的响应式数据。

let tom: Person = {
   age: 30,
   name: "cat"
}
//1。通过ref声明响应式数据
const ref1 = ref<number>(1);//基础类型数据
const ref2 = ref<Person>(tom); //对象/数组类型数据

//2. 通过reactive声明相适应数据
const number = reactive(1);//报错不能声明基本类型的数据,只能传入一个对象。
const reactive1 = reactive(tom);//可以

reactive() 函数 reactive()函数内部通过 New Proxy()的方式来对数据进行代理。从而实现响应式。由于Proxy的限制,只能用来创建对象类型的响应式数据。 通过reactive()函数,会得到一个代理对象(也叫响应式对象),只有通过代理对象操作属性,才是响应式的,通过原对象操作属性是不会响应。

let tom: Person = {
   age: 30,
   name: "cat"
}

const reactive1 = reactive(tom); //返回代理对象。(也叫响应式对象)

reactive1.name = "cat";//操作代理对象,响应式。
tom.name = "mi"; //操作原对象,不是响应式。
let tom: Person = {
   age: 30,
   name: "cat"
}
const reactive1 = reactive(tom);
const reactive2 = reactive(tom);

const reactive3 = reactive(reactive2);

console.log(reactive1 == reactive2); //true
console.log(reactive3 == reactive2); //true
console.log(reactive3 == reactive1); //true
let tom: Person = {
   age: 30,
   name: "cat",
   sex: {
      ho: "02",
      sex: "男"
   },
}
const reactive1 = reactive(tom);

console.log(reactive1.sex); //通过打印即可验证,打印出来的是一个响应式对象
  1. 只能处理对象类型:

    • 由于Proxy的限制,reactive()函数只对对象类型的数据有效,对于基本数据类型的代理是无效的。
  2. 不能更改响应式对象引用:

    • 因为 Vue 的响应式系统是通过 属性 访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。(Vue官网解释)
    • 意思是:将响应式对象的变量赋值给另一个对象,丢失响应式。(修改嵌套的响应式对象里面嵌套对象引用可以,因为是深层代理。
    • 通过ref函数生成的可以,)后面介绍。
    let tom: Person = {
      age: 30,
      name: "cat"
    }
    let reactive1 = reactive(tom);
    
    reactive1 = {
      age: 0,
      name: "cat"
    }//将响应式对象的变量赋值给另一个对象,丢失响应式
    
    //修改嵌套响应式对象里面嵌套的对象引用可以,因为是深层代理。
    let tom: Person = {
      age: 30,
      name: "tom",
      sex: {
         ho: "02",
         sex: "男"
      },
    }
    let reactive1 = reactive(tom);
    
    reactive1.sex = {
      ho: "03",
      sex: "女"
    }
  3. 属性赋值失去响应式:

    • 当我们将响应式对象的 属性 赋值给另一个变量、使用解构赋值、将属性传入一个函数中,我们会失去响应性。

    • 只对基本数据类型有限制。如果属性的类型是对象类型,则不受限制。

    • 原因:

      基本数据类型是值传递,而对象类型是引用传递。

      • 因为值传递的本意是复制值,就是简单的复制一个值给另一个变量。
      • 而对象类型的数据不会失去响应式是因为:1.对象是引用传递,复制的是对象的引用。2.在进行代理的时候是深层次的代理,因此该对象自身就是响应式对象。
    let tom: Person = {
      age: 30,
      name: "cat"
    }
    let reactive1 = reactive(tom);
    let ng: string = reactive1.name; // 将属性赋值给另一个变量,ng变量不是响应式。
    let {age, name} = reactive1; // 使用解构赋值,得到的属性不是响应式。
    
    setName(reactive1.name);//将属性传入函数中,该属性不是响应式
    function setName(name: string) {
      name = "cat02"
    }
    
    //属性是一个对象类型。
    let tom: Person = {
      age: 30,
      name: "cat",
      sex: {
         ho: "02",
         sex: "男"
      },
    }
    let reactive1 = reactive(tom);
    
    setName(reactive1.sex)
    function setName(sex: Sex) {
      sex.sex = "女";//可以修改,是响应式。
    }
    
    let {sex: sex1} = reactive1;
    
    sex1.ho = "sssss";//可以修改,是响应式。

ref() 函数 为了解决 reactive() 带来的限制,Vue 也提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref对象。 通过ref()函数创建响应式的数据,会返回一个RefImpl对象(也叫ref对象),响应式数据会被封装到ref对象.value属性中。(.value属性就是响应式属性)

如果是基本类型的数据,那么ref函数就会使用Object.defineProperty()来实现对数据的劫持。

如果是对象(数组)类型的数据,则ref函数就会调用reactive()函数。而.value就是代理对象

let tom: Person = {
   age: 30,
   name: "tom",
}

const ref1 = ref(1);//基本类型
console.log(ref1.value);

const ref2 = ref<Person>(tom);//对象类型
console.log(ref2.value); //可以看到时一个代理对象

使用ref()函数创建响应式的数据,是可以更改对象的引用的。(通过reactive函数的是不能替换)

const person = ref<Person>({
   age: 20,
   name: "tom"
});

let tom: Person = {
   age: 30,
   name: "cat"
}
person.value = tom;//可以替换,因为当值发表改变了就会调用reactive函数。

一言以蔽之,ref() 使我们能创造一种任意值的 “引用” 并能够不丢失响应性地随意传递。这个功能非常重要,因为它经常用于将逻辑提取到 组合函数 中。(Vue官网)

🔆 reactive 和 ref的区别


✅ 个人理解

  1. 为什么 ref() 创建的响应式数据,可以更改对象的引用,而 reactive() 不可以? 答: 从本质上讲两者创建的响应式数据都是不可以被更改引用的。响应式对象被创建的时候就会被注入到响应系统内部,如果强行覆盖的话,相当于抛弃了响应式对象而去转而使用一个非响应式的对象。

    • ref() 创建了一个响应式对象,其主要内容都被挂载在了 value 属性上,因此修改 value 属性上的引用并没有覆盖 ref 创建的响应式对象,所以我们说 ref 是可以修改对象引用的。
    • reactive() 创建响应对象的方式不太一样,对于一个对象而言,它会将对象内所有属性注册 (且深层递归注册) 为响应式,但是对于顶层对象本身而言,它不是响应式的。因此,我们假如覆盖这个对象,相当于把该对象内所有的响应式属性都抛弃了,也就失去了响应性。(将该对象引用赋值给其他变量也是同理,新变量获得的是非响应式对象的引用,因此也不在响应系统的服务范围内)
  2. 什么情况下赋值不会丢失响应? 答: 首先我们要清楚为什么会丢失响应,无非是你当前操作的变量它没有在响应系统中注册,那么什么变量算是在响应系统中注册的呢?通过 Vue 提供的 API 接口 (例如 ref / reactive 等) 生成的变量。当我们将这些变量赋值给新创建的变量时,由于新创建的变量本身是没有在响应系统中注册过的,所以它不具备响应性,但是!赋值又分为两种情况,值传递和引用传递:

    • 值传递:复制值,丢失响应
    • 引用传递:复制引用地址,最终调用变量时仍指向响应系统中的内存地址,保留响应

因此,当我们复制一个对象引用时 (此处包括 ref 对象类型的嵌套对象,因为 vue 对它进行了深层代理),会保留其响应性;当我们复制一个基本类型时 (例如从 ref 对象属性中抽出一个基本类型值赋给新的变量),就会丢失响应。