一文搞懂Vue3的响应式函数ref和reactive

4,845 阅读6分钟

Vue3-响应式函数-ref和reactive函数的使用

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

1. 声明响应式数据

在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);//可以

2. reactive()函数

reactive()函数内部通过 New Proxy()的方式来对数据进行代理。从而实现响应式。由于Proxy的限制,只能用来创建对象类型的响应式数据。

通过reactive()函数,会得到一个代理对象(也叫响应式对象),只有通过代理对象操作属性,才是响应式的,通过原对象操作属性是不会响应。

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

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

reactive1.name = "cat";//操作代理对象,响应式。
tom.name = "mi"; //操作原对象,不是响应式。

2.1 reactive的单例模式:

reactive()函数的设计是单例模式,也就是说:对同一个对象多次调用 reactive() 函数,返回的都是同一个代理对象。对一个代理对象调用reactive() 函数,总会返回代理对象自身。

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理对象。

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

2.2 reactive的深层代理

reactive()是深层次的代理,响应式对象内的嵌套对象依然是代理对象。并且也是单例模式。(Proxy是浅代理,对于嵌套的对象的不代理的)

let tom: Person = {
   age: 30,
   name: "cat",
   sex: {
      ho: "02",
      sex: "男"
   },
}
const reactive1 = reactive(tom);

console.log(reactive1.sex); //通过打印即可验证,打印出来的是一个响应式对象

2.2 reactive()函数的局限性

  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";//可以修改,是响应式。
    

3. 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官网)

4. reactive 和 ref的区别

  • reactive只能用来创建对象类型的响应式数据,而ref则是可以创建任意数据类型的响应式对象。
  • reactive创建的是proxy的代理对象,而ref创建的则是ref对象
  • 通过reactive创建的响应式对象是不能更换对象的引用的,而通过ref创建响应式对象是可以替换的。
  • reactive是通过Proxy来对数据进行代理的,而ref,如果是基本数据类型,通过Object.defineProperty()来实现对数据的劫持。如果是对象类型的数据,则是通过调用reactive函数实现。