Vue 3 Composition API中必知的Ref与Reactive的区别

2,423 阅读6分钟
  • Ref()和Reactive()是Vue 3 Composition API 中引入的创建响应式属性的新方法。

  •  它们是包装对象,可以用内部值初始化并分配给变量。

  • 在Vue 3中,我们需要先导入所需的包,然后再在组件中使用它。

  • 我想你已经知道(_如何使用Vue CLI启动和运行Vue JS 3项目:这是一个链接)_了。

Ref()

  • 我们可以像通常在设置函数中那样创建一个变量,并将其添加到返回的对象中。

  • 然后在模板中渲染它。

  • 这将会起作用,但没有响应性。

  • 我们可以在不失去响应性的情况下创建一个属性,其中一个方法是使用ref()。

  • ref()对象接受一个内部值,并返回一个响应性和可改变的对象。

  • 这对于原始类型的单一变量,如String、Boolean、Number等,是非常好的。

  • 它有一个名为.value的单一属性,指向内部值,这就是我们如何获得和设置属性的值。

在顶部导入ref包

import  { ref } from 'vue'; 

count变量持有一个内部值为0的ref()对象。

let count = ref(0); 

ref()对象将有一个名为value的单一属性,它指向内部值,在本例中是0。

为了获得或设置count变量的值,我们可以使用变量的属性.value来解开它的值。

console.log(count.value); // 0 get 
count.value = 12 // 12 set

然后,我们可以像下面这样通过返回setup()函数来渲染计数变量。

正如你在下面的代码中注意到的,count属性在模板中被渲染,而没有使用.value属性。

这是因为当Ref对象被添加到setup函数返回的对象中时,当我们在模板中使用它时,它会自动解除内部值的包装。

<template>
   {{count}}
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    let count = ref(0);
    return {
      count,
    };
  },
};
</script>

为了检查count属性的反应性,我们给一个按钮元素附加一个点击事件。

然后我们给count属性添加一个数字,以1为单位递增。

<template>
  <div
    style="
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;"
  >
    <el-button type="danger" @click="countNumber">Count</button>

    <div>{{ count }}</div>
  </div>
</template>


<script>
import { ref } from "vue";
export default {
  setup() {
    let count = ref(0);

     function countNumber() {
      count.value++;
    }

    return {
      count,
      countNumber
    };
  },
};
</script>

那么响应性和预期的一样。

Reactive()

reactive()也是一个封装对象,它接收一个对象并返回原对象的一个响应性代理。

它对字典结构的类型非常好,比如JS Object。

在顶部导入reactive包。

import  { reactive } from 'vue'; 

这与ref对象非常相似,但内部的值应该是字典结构的数据,如JS对象,而不是一个单一的值。

let count = reactive({val: 0}); 

使用代理对象,我们可以像平时那样访问内部对象的属性。

console.log(count.val);

为了使这个对象具有响应性,我们所要做的就是在按钮点击事件的回调函数中把val属性递增1。

其余的都是一样的。

<script>
import { reactive } from "vue";
export default {
  setup() {
    let count = reactive({val:0});

    function countNumber() {
      count.val++;
    }

    return {
      count,
      countNumber,
    };
  },
};
</script>

这样就有了一个相同的结果。

好吧,我有一个作妖的想法,如果我对一个单一的内部值使用reactive呢?

先给出答案:该变量将失去其反应性。

当你用一个单值的reactive包装对象创建一个变量时,在浏览器的控制台会有一个警告:

value cannot be made reactive: 0

再来,反过来说,我如果用ref()而不是reactive()来处理JavaScript对象,它可以正常工作吗?

答案是肯定的。

我可以用ref()而不是reactive()来处理JavaScript对象,它可以正常工作,但在后台,**ref()求助于reactive(),**它将使用reactive包装对象来保持内部数据的响应性。

<script>
import { ref } from "vue";
export default {
  setup() {
    let count = ref({ val: 0 });

    function countNumber() {
      count.value.val++;
    }

    return {
      count,
      countNumber,
    };
  },
};
</script>

你可能会问......好吧,我可以用ref对象来创建我所有的变量,因为它们可以处理单值类型或复杂的数据类型。

这也行得通,但在获取或设置变量数据时,到处使用.value属性会很繁琐。

(为了增加文章的趣味性和八卦性,在这儿说一个八卦:我们团队,包括架构都是满篇的ref。我们是前端微服务,子项目很多,我也去看过其他子服务的前端代码,就发现有一个小哥是正常使用的,他是:单值类型的用ref,复杂的数据类型用reactive。但是包括我自己,从去年3.2一出来,就把vue3.2用在实际的大型项目上,在做响应式数据的时候,不管简单类型的数据,还是复杂类型的数据,都是用ref,.value漫天飞。不过到处使用.value属性会特别繁琐,所以今年我就是:单值类型的用ref,复杂的数据类型用reactive

为了限制到处使用.value属性会很繁琐这一点,建议大家只对单值类型(如字符串、数字、布尔等)使用ref。 在处理复杂的对象如对象、数组等时,使用reactive,感觉是正确的。 还有我注意到的一点是,无论使用ref还是reactive,一个数组类型的变量都不会失去它的响应性,因为数组在 JavaScript上是一个对象。

使用异步数据

使用ref()方法,我们可以很快做到这一点。 

 我使用setTimeout()来模拟API调用。

<template>
  <div
    style="
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    "
  >
    <div>
      ID: {{ user.id }} <br />
      Name: {{ user.name }}
    </div>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    let user = ref({ id: 0, name: "小明" });

    setTimeout(() => {
       user.value = {
        id: 20,
        name: "xiaomingming",
      };
    }, 2000);

    return {
      user,
    };
  },
};
</script>

结果正如你在上面的例子中看到的,新的数据可以在2秒后呈现在DOM中,非常直接。 让我们看看如何使用reactive()方法来完成这个任务。

<template>
  <div
    style="
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    "
  >
    <div>
      ID: {{ user.id }} <br />
      Name: {{ user.name }}
    </div>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  setup() {
    let user = reactive({ id: 0, name: "小明" });

    setTimeout(() => {
       user = {
        id: 20,
        name: "xiaomingming",
      };
    }, 2000);

    return {
      user,
    };
  },
};
</script>

在上面的代码中,失去了对用户对象的响应性,因为我们不能直接改变整个对象。(这儿其实相当于我给用户对象user重新开辟了一个内存空间,改变了user的引用地址)

所以,相反,我们只能改变用户对象的属性。那么怎么办呢,怎么能又想使用reactive又不是去对用户对象的响应性呢?

往下看。在下面的代码中,我单独声明了用户对象,然后使用用户属性将其添加到reactive()方法的一个内部对象。 然后我可以很容易地在setTimeout()函数中给该属性分配新的数据,该函数将在2秒后改变数据。

<template>
  <div
    style="
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    "
  >
    <div>
      ID: {{ state.user.id }} <br />
      Name: {{ state.user.name }}
    </div>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  setup() {
    let user = { id: 0, name: "小明" };

    let state = reactive({ user });

    setTimeout(() => {
      state.user = {
        id: 20,
        name: "xiaomingming",
      };
    }, 2000);

    return {
      state,
    };
  },
};
</script>

当然,你可能想说(或许你很乖,不说,哈哈哈),那你又另外声明了state,而且在模板里面,你也不是要通过 state.user.idstate.user.name 来将数据写入到模板吗?多了state.user.,那如果属性多了,也不是state.user漫天飞吗?

相信我,当业务场景多,业务逻辑复杂的时候,在模板里面多写几个 state.user.远比script里面到处使用.value属性会很简洁很多很多,而且在使用.value也会搞错哪些该加value,哪些不该加value.

总结

到这儿,总结一波~~

我希望这篇文章澄清了ref()reactive()包装对象之间的一些关键区别,以及如何在Vue 3 Composition API中使用它们来创建变量而不失去其响应性。 

 我还向你展示了什么时候应该选择它们其中的一个。 

 如果你有任何问题,请随时在下面评论,我会尽快回复你。

最后:

Happy Coding!