持续创作,加速成长!这是我参与「掘金日新计划 · 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.id 和 state.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:
-
作用:标记一个对象,使其永远不会再成为响应式对象。
-
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
-
总结:
这篇文章总结了如何在Vue 3使用常见的 Composition API,并且使用它们来创建变量并且不失去其响应性。