Vue 3中的常见 Composition API以及它们的区别

196 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

Vue 3中的常见 Composition API以及它们的区别

Composition API : 一组低侵入式的、函数式的 API,它使我们能够更灵活地「组合」组件的逻辑。

主要的区别就是,vue2中Options API的对一个功能的逻辑代码比较分散,当项目大了,就非常的难以维护。 而在Vue3中的Composition API 就很好的解决了,此类问题,阅读性更加的强,如果项目大了,还可单独提出来,封装成一个函数,放在其他的文件。

  • Ref()和Reactive()是Vue 3 Composition API 中引入的创建响应式属性的新方法。
  • 它们是包装对象,可以用内部值初始化并分配给变量。
  • 在Vue 3中,我们需要先导入所需的包,然后再在组件中使用它。

setup()

​ composition API提供的函数都是在setup中使用的,所以需要了解setup这个函数。

setup函数,就像Options API中使用, 增加一个属性。setup执行的时期

export default defineComponent({
      beforeCreate() {
        console.log('beforeCreate', this);
      },
      created() {
        console.log('created', this)
      },
      setup() {
        console.log('setup', this);
      }
})

在Vue2中,我们知道,beforeCreate是最先执行的生命周期函数。 但是在vue3中,setup最先执行。并且其中的this为undefined,是因为setup的调用发生在data property, computed property或methods解析之前,所以在setup中拿去不到。

setup的参数

setup接受两个参数:setup(target,propsName)

  • 第一个参数:props 父组件传递过来的
  • 第二个参数:是一个对象,包含三个可用的属性 需要注意的时,我们接受父组件传递出来的props,还是跟Options API中一样的定义Props,又因为在setup中拿不到this,所以第一个参数提供props,为了让我们拿到props。

Ref()

  • 我们可以像通常在设置函数中那样创建一个变量,并将其添加到返回的对象中,然后在模板中渲染它,但是它没有响应性。
  • 我们可以在不失去响应性的情况下创建一个属性,其中一个方法是使用ref()。
  • ref()对象接受一个内部值,并返回一个响应性和可改变的对象。
  • 这对于原始类型的单一变量,如String、Boolean、Number等,是非常好的。
  • 它有一个名为.value的单一属性,指向内部值,这就是我们如何获得和设置属性的值。

在顶部导入 ref API

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 API。

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。

shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。--浅层,只处理第一层

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。

toRaw 与 markRaw

  • toRaw:

    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:

    • 作用:标记一个对象,使其永远不会再成为响应式对象。

    • 应用场景:

      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

总结:

这篇文章总结了如何在Vue 3使用常见的 Composition API,并且使用它们来创建变量并且不失去其响应性。