什么是 Composition API?
Composition API 也叫组合式API, 是在vue3中新引入的一种API,vue2中已经有option API了,那为什么要新稿这么一套呢,其实主要原因是要解决vue2中的option API的在处理复杂组件逻辑的局限性,例如逻辑分散、代码复用性差、类型推断困难、组件组织混乱、响应式系统限制、模板逻辑复杂性、组件测试困难等问题。为此Composition API通过函数的方式来组织代码,使得逻辑更加模块化和可组合,这就变得很灵活。
核心概念
setup 函数
setup 是使用组合式API的入口函数,用于替代vue2中的data、computed 、methods等选项,setup函数参数为(props, context)props可以理解为vue2中的props属性,用来访问父级传来的参数值。
context 则是一个对象集合包括:
- attrs:包含了父组件传递给子组件的所有属性(非prop的属性),它们是响应式的,可以动态地绑定到模板中。
- slots:包含了所有传入的插槽内容,这些内容可以用于渲染作用域插槽。
- emit:是一个函数,用于向父组件发出自定义事件。它是this.$emit的替代。
- expose:是一个函数,用于显式地暴露子组件内部的属性或方法,使得父组件可以通过ref访问到这些属性或方法。
setup声明方式有两种语法方式
第一种
代码语言:javascript复制<script>
export default {
props: {
msg: String
},
setup(props) {
console.log('setup',props.msg)
}
}
</script>
第二种 更简洁 官方称为更符合人体工程学
但 props 要更改写法,使用defineProps,放心虽然没有声明,但它将自动在setup 中可用
代码语言:javascript复制<script setup>
defineProps({
msg: String,
})
</script>
核心api
ref
ref 用于创建一个可修改的响应式的基本数据类型或引用(可以对 .value 赋值来进行修改, 如果将一个对象赋值给 ref,那这个对象则通过 reactive 转化为深层次响应式的数据),
如果这个对象中存在ref也会被深层解包,为避免这种深层次的转化,官方建议用 shallowRef替代
使用方法
代码语言:javascript复制// .vue
<template>
<h1>{{age}}</h1>
</template>
<script setup>
import { ref } from 'vue'
let age = ref(18)
age.value = 20
</script>
reactive
reactive 用于创建一个响应式对象。有一点需要注意,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
只有当你直接访问这个ref内部的值时,比如使用.value属性,Vue才会“解包”这个ref,把它内部的值当作响应式值进行追踪。
这样做的好处是提高了性能,避免了不必要的响应式转换,因为在某些情况下,你可能并不需要数组或Map中的每个ref元素都是响应式的。
所以要避免深层响应式的转换,只想保留对这个对象顶层次访问的响应式,可以用shallowReactive做为替代
代码语言:javascript复制import { ref, reactive } from 'vue'
let age = ref(18)
let obj = reactive(['123',age])
// 直接访问数组中的 obj 元素,不会执行解包
console.log(obj[1]); // 输出是 ref 对象本身,而不是 18
// 要获取 obj 中的值,需要使用.value
console.log(obj[1].value); // 输出 18
</script>
readonly
readonly和 reacitve 类似,只不过从字面意义上就不难理解,only 它是只读的,而且代理是深层的,所以同理要避免深层层级的转换行为,可以用shallowReadonly 来替代
代码语言:javascript复制...
<!--19-->
<div>{{ man.age }}</div>
<!--19-->
<div>{{ man2.age }}</div>
<!--20-->
<div>{{ man3.age }}</div>
...
import { reactive, ref, readonly, watchEffect } from 'vue'
...
let man = reactive({ age: 18 })
let man2 = readonly(man)
let man3 = readonly({age: 20})
watchEffect(() => {
// 用来做响应性追踪
console.log(man.age)
})
// 会触发 watchEffect
man.age
// 下面man2和man3的操作会执行失败而且会触发警告
// Set operation on key "age" failed: target is readonly.
man2.age
man3.age
computed、watch
computed、watch和vue2中的含义相同
computed用法
代码语言:javascript复制...
<h1 @click="count ">{{ addCount }}</h1>
...
import { ref, reactive } from 'vue'
...
let count = ref(1)
const addCount = computed(() => count.value 1)
// addCount.value // 错误写法,因为addCount是只读的 会报错
watch 默认懒监听: 仅在监听源发生变化时才执行回调函数
watch一共三个参数,watch(source,callback,options)
- source: 这个参数是要侦听的响应式引用或响应式对象的属性,或者是返回响应式值的getter函数。 可以是 ref、reactive 对象、computed 计算属性或者一个自定义的getter函数。
- callback:当侦听的源发生变化时会被调用的回调函数。这个函数接收三个参数:新值、旧值和onCleanup函数。onCleanup可以用来注册清理回调,在下次侦听器执行前会被调用。
- options (可选): 包含配置选项json对象
- immediate: 值为true,会在侦听器创建时立即执行回调。
- deep: 值为true 会深度监听对象内部的变化。
- flush: 指定回调函数的执行时机
- post (默认值): 侦听器回调会在 DOM 更新之后执行。
- pre: 与post相反,表示侦听器回调会在 DOM更新之前执行 的更新。这个选项适用于需要在 DOM 更新之前访问旧 DOM 的场景。
- sync: 表示侦听器回调会在数据变化时立即同步执行。这通常会导致更高的性能开销,因为它会阻止其他任务的执行,直到侦听器回调完成。这个选项适用于需要立即响应数据变化,并且变化不频繁的场景。
- onCleanup: 一个在侦听器停止侦听之前执行的函数(可以用来清除无效的副作用,例如等待中的异步请求。)
- onTrack: 在依赖项被追踪时触发
- onTrigger: 在依赖项的值发生变化并触发更新时触发
import { ref, watch } from 'vue';
const data = ref(0);
watch(data, (newValue, oldValue, onCleanup) => {
let timeoutId;
onCleanup(() => {
clearTimeout(timeoutId);
});
timeoutId = setTimeout(() => {
console.log(`数据变化了,新值是 ${newValue}`);
}, 1000);
}, {
immediate: true,
onTrack: (dep, info) => {
console.log(`在依赖项被追踪时触发:${dep}`);
},
onTrigger: (dep, newValue, oldValue) => {
console.log(`依赖项触发更新:${dep},新值为 ${newValue},旧值为 ${oldValue}`);
}
});
watchEffect()
自动追踪依赖:当你需要根据多个响应式数据的变化来执行某些操作时,不需要显式地指定依赖,watchEffect() 会自动追踪
立即执行:watchEffect()在组件初始化时会立即执行一次,确保依赖状态的最新值被正确应用。
无需关心具体响应式属性:当你不需要关心响应式数据具体是哪个属性变化,只是想在其变化时做一些事情时。
代码和效果图如下
代码语言:javascript复制...
<div>
<input v-model="num1" type="number">
<input v-model="num2" type="number">
<h1>结果:{{ result }}</h1>
</div>
...
...
import { ref, watchEffect } from 'vue'
let result = ref(0)
let num1 = ref(0)
let num2 = ref(0)
watchEffect(() => {
result.value = num1.value num2.value
});
...
组合函数(Composables)
组合函数是使用 Composition API 编写的函数,用于封装和复用逻辑。
生命周期钩子
Composition API 提供了与 Options API 对应的生命周期钩子函数,如 onMounted、onUnmounted 等。
逻辑分散:当组件变得复杂时,相关的逻辑可能分布在data、computed、methods、watch等不同的选项中,导致同一功能的代码片段被拆分到各个部分,增加了代码的查找和理解难度。
代码复用性差:Options API中,如果想要复用代码,通常需要创建混入(mixins),但混入会带来命名冲突、依赖关系不明确等问题,使得代码复用变得复杂且难以维护。
类型推断困难:在Vue 2中,Options API并不支持TypeScript的某些高级类型推断功能,这限制了在大型项目和复杂组件中使用TypeScript的能力。
组件组织混乱:随着组件的复杂性增加,组件的选项列表也会增长,这可能导致组件的选项组织混乱,难以清晰地识别和理解组件的结构。
响应式系统限制:Vue 2的响应式系统基于Object.defineProperty,对于数组或嵌套对象的处理不够高效,且不支持一些现代JavaScript语法(如 Proxy)提供的更强大和灵活的响应式特性。
模板逻辑复杂性:在Vue 2中,有时为了实现某些复杂的逻辑,需要在模板中写大量的逻辑代码,这违反了关注点分离的原则,使得模板变得复杂且难以维护。
组件测试困难:由于逻辑分散,组件测试变得复杂,测试每个功能时可能需要模拟多个不同的选项状态,增加了测试的难度和复杂性。,
是 Vue 3 引入的一种新的 API,旨在解决 Options API 在处理复杂组件逻辑时的局限性。它通过函数的方式来组织代码,使得逻辑更加模块化和可组合。
Vue 3 的 Composition API 是一种新的方式来定义和组织组件的逻辑,它提供了更灵活、可组合和可重用的代码结构。相比于 Vue 2 的 Options API,Composition API 使得在大型项目中管理复杂逻辑变得更加容易。以下是对 Vue 3 Composition API 的详细解释: