Skip to content

一文搞懂 vue3 中 ref 和 reactive 的区别

1.ref 和 reactive 的基本介绍和区别

refreactive 都用于创建在 Vue 3 中实现响应式数据的方式,但它们的用法和一些细节上有所不同。

  1. ref ref 一般用于将基本类型数据(如数字、字符串等)转换为响应式数据**(注意:实际上可以创建任意类型,引用类型也可以)**。ref 创建的数据会被包装在一个对象中,可以通过 .value 来访问和修改数据。

    js
    import { ref } from "vue";
    
    const count = ref(0); // 创建一个响应式的数字
    console.log(count.value); // 访问数据
    count.value++; // 修改数据
  2. reactive reactive 用于将对象转换为响应式数据。它可以使整个对象的所有属性都变成响应式的,这包括对象的嵌套属性(因为做了深层代理)。你可以直接访问和修改对象的属性,无需使用 .value

    js
    import { reactive } from "vue";
    
    const state = reactive({ count: 0 }); // 创建一个响应式对象
    console.log(state.count); // 访问属性
    state.count++; // 修改属性

对比

reactive 和 ref 的区别

  • reactive只能用来创建对象类型的响应式数据,而**ref则是可以创建任意数据类型的响应式对象**。
  • reactive创建的是proxy的代理对象,而 ref 创建的则是ref 对象
  • 通过reactive创建的响应式对象是不能更换对象的引用的,而通过ref创建响应式对象是可以替换的
  • reactive是通过 Proxy 来对数据进行代理的,而**ref如果是基本数据类型,通过Object.defineProperty()来实现对数据的劫持。如果是对象类型的数据,则是通过调用reactive函数实现(所以也是通过了 Proxy)**。 ——> 这里注意要区分情况来说!

2.reactive 的局限性

  1. 只能处理对象类型:
  • 由于Proxy的限制,reactive()函数只对对象类型的数据有效对于基本数据类型的代理是无效的
  1. 不能更改响应式对象引用:——> 最关键的:所以在接受 ajax 返回值的时候我们一般不要用 reactive 的响应式对象
  • 因为 Vue 的响应式系统是通过 属性 访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。(Vue 官网解释)

  • 意思是:将另一个对象赋值给响应式对象的变量,丢失响应式(修改嵌套的响应式对象里面嵌套对象的引用可以,因为是深层代理),但是通过ref函数生成的响应式对象可以直接修改引用。

    js
    //1.直接修改响应式对象的引用
    let obj = reactive({}); //这个是响应式的obj
    obj = {
      a: 1,
      b: 2,
    }; //这个obj就不是响应式的了
    
    //2.修改嵌套的响应式对象里面嵌套对象的引用
    let obj = reactive({
      //这个是响应式的obj
      a: 1,
      b: {
        str: "2",
      },
    });
    obj.b = {
      //这样obj还是响应式的
      c: 2,
    };
  1. 属性赋值失去响应式:——> 所以需要使用 toRefs 这个 API!
  • 当我们将响应式对象的基本类型的 属性 (赋值给另一个变量、使用解构赋值、将属性传入一个函数中),我们会失去响应性。

  • 只对基本数据类型有限制。如果属性的类型是对象类型,则不受限制,因为里面的对象都是响应式的。

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

    • 因为值传递的本意是复制值,就是简单的复制一个值给另一个变量。

    • 而对象类型的数据不会失去响应式是因为:

      1.对象是引用传递,复制的是对象的引用。

      2.在进行代理的时候是深层次的代理,因此该对象本来就是响应式对象

3.ref 的好处

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

为了解决 reactive() 带来的限制,Vue 也提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref对象。

通过ref()函数创建响应式的数据,会返回一个RefImpl对象(也叫ref对象),响应式数据会被封装到ref 对象.value属性中。(.value属性就是响应式属性)

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

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

所以说 ref 可以应付任何的场景,里面定义引用数据类型也是完全可以直接实现响应式的!

4.有场景是 reactive 能做而 ref 做不了的吗?

问题:我们在使用响应式 API 时一直被告知 ref 不仅可以用于创建基础类型的响应式,也可以创建引用类型的响应式。而对于引用类型,ref 的底层也是转换为 reactive 来进行响应式处理。那既然这样为撒还需要 reactive ,全部使用 ref 不就行了吗?难道有什么场景是 reactive 能做而 ref 做不了的?

通过跟踪 Vue3 的源代码可以证明,当我们调用 ref 方法来定义响应式数据时,当参数为对象类型时,其实里面用的是 reactive 方法。也就是说上面的 data.value ,事实上是 reactive 方法创造出来的。

所以说:reactive 能做的 ref 也能做(并且还是用 reactive 做的)!没有 ref 做不了的!

简单来说 ref 是在 reactive 上在进行了封装进行了增强,所以在 Vue3 中 reactive 能做的,ref 也能做,reactive 不能做的,ref 也能做。

5.一般适合用 reactive 的地方

1.在对象不需要修改引用的情况下,省去.value 的写法

因为 ref 和 reactive 对于引用数据类型都可以实现完全的响应式,但是 reactive 在使用对象的时候不需要.value,可以减少一点代码,所以在定义了响应式数据类型的对象之后如果不需要修改这个引用的话(因为 reactive 一旦修改引用就会造成数据变成不响应了!),我们就可以用 reactive 来定义!

2.用来和 toRefs 配合使用,定义 ref 对象

reactive 是用来定义更加复杂的数据类型,但是定义后里面的变量取出来就不再是响应式 Ref 对象数据了,所以需要用 toRefs 函数把每一个属性转化为响应式数据对象 ——> 这也是常见的用法,省去了写太多的 ref,简化写法

img

将上面用 ref 写的代码转化成 reactive 型的代码

vue
<template>
  <!-- <img alt="Vue logo" src="./assets/logo.png"> -->
  <div>
    <h1>{ { name }}</h1>
    <h1>{ { age }}</h1>
    <button @click="sayName">按钮</button>
  </div>
</template>

<script lang="ts">
  import { computed, reactive, toRefs } from "vue";

  interface DataProps {
    name: string;
    now: number;
    birthYear: number;
    age: number;
    sayName: () => void;
  }

  export default {
    name: "App",
    setup() {
      const data: DataProps = reactive({
        name: "zhangsan",
        birthYear: 2000,
        now: 2020,
        sayName: () => {
          console.log(1111);
          console.log(data.name);

          data.name = "I am " + data.name;
          console.log(data.name);
        },
        age: computed(() => {
          return data.now - data.birthYear;
        }),
      });

      const refData = toRefs(data);
      refData.age;
      return {
        ...refData,
      };
    },
  };
</script>

<style>
  #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>