Composition API 是 Vue 3 中引入的一种新的组件编写方式,与 Vue 2 中的选项式 API(Options API)相对。它允许开发者以更加灵活和组合的方式来组织和管理组件的逻辑,提高了代码的可读性和可维护性。
特点和优势
-
逻辑复用:
- 使用 Composition API,逻辑可以通过组合函数被复用,这样可以避免在选项式 API 中使用 mixins 带来的命名冲突和不透明性问题。
-
更好的类型推导:
- 与 TypeScript 配合使用时,Composition API 能够提供更好的类型推导,增强了开发体验。
-
组织结构清晰:
- 可以将相关的逻辑和状态组合在一起,而不必被分散在不同的选项中(如 data、computed、methods)。
-
简化响应式状态管理:
- 提供了
ref和reactive等 API,更容易创建和管理响应式状态。
- 提供了
缺点
-
学习曲线:
- 对于习惯于选项式 API 的开发者,尤其是 Vue 2 的用户,学习 Composition API 可能需要一定的时间和实践。其概念和用法与选项式 API 有显著不同。
-
代码可读性:
- 虽然 Composition API 提供了更高的灵活性,但对于一些简单的组件来说,代码可能会变得较为复杂。某些开发者可能会觉得,选项式 API 在简单情况下更易于理解。
-
箭头函数的使用:
- 在 Composition API 中,常用的组合函数和逻辑会以箭头函数的形式存在,可能导致一些开发者在处理
this语境时产生困惑。这点在使用复杂的对象时可能会引发问题。
- 在 Composition API 中,常用的组合函数和逻辑会以箭头函数的形式存在,可能导致一些开发者在处理
-
状态管理分散:
- 由于相关逻辑可以被拆分到不同的组合函数中,可能导致状态的管理和来源变得分散,增加了理解和追溯的难度。在大型应用中,可能会需要额外的工作来清晰地界定状态来源。
-
初始设置复杂性:
- 对于初级开发者,在设置状态、计算属性和侦听器时,可能需要更多的样板代码,特别是在涉及多个响应式数据时。
-
调试困难:
- 由于 Composition API 允许更频繁地创建抽象和组合逻辑,在调试时,可能难以快速确定某个状态或逻辑的来源,特别是在较大或复杂的组件中
总体而言,尽管 Composition API 在灵活性和逻辑复用方面具有显著优势,但也带来了一些复杂性和学习上的挑战。开发者需要根据实际项目和团队的需求,在 Composition API 和选项式 API 之间做出选择,以便在灵活性和可维护性之间找到平衡(但是Vue3开发项目基本上也是推荐Composition API了)。接下来解密每一个常用的API:
Composition API
1. ref:
ref 用于创建一个基本类型(如字符串、数字、布尔值等)的响应式引用,使得 Vue 能够追踪其变化并触发视图的更新,使用 ref 创建的变量需要通过 .value 属性访问和更新。ref 主要用于支持基本数据类型的响应式数据,比如数字、字符串和布尔值等。当你需要将这些基本类型存储在响应式状态中时,可以使用 ref
<script setup lang="ts">
import { ref } from 'vue'
// ref用法 ts类型可传入一个泛型或者自行推导
const count = ref('abcedef')
console.log(count.value, '自带类型推导')
const num = ref<string | number>(0)
console.log(num.value, '泛型约束 内部 T')
</script>
2. reactive:
reactive 用于将一个普通的 JavaScript 对象转换为响应式对象,使 Vue 能够监测其属性的变化并自动更新视图,使用 reactive 创建的对象是深度响应式的。这意味着对象内部的嵌套属性也会被转换为响应式,任何层级的属性变化都会被 Vue 的响应式系统跟踪。与 ref 不同,使用 reactive 创建的对象可以直接访问其属性,而不需要使用 .value。这使得代码更加简洁和易读。reactive 适合用于存储复杂的数据结构,比如对象和数组。对于简单类型的数据则更推荐使用 ref
<script setup lang="ts">
import { reactive } from 'vue'
// 方式一
const state = reactive({
count: 0,
obj: {
name: '张三',
age: 18,
},
})
console.log(state.count, '自行推导类型')
// 方式二
interface Person {
name: string
age: number
sex: string
}
const obj = reactive<Person>({
name: '张三',
age: 18,
sex: '男',
})
console.log(obj.name, '泛型约束 内部 T extends object')
// 方式三
const obj2: Person = reactive({
name: '张三',
age: 18,
sex: '男',
})
console.log(obj2.name, 'ts类型约束')
</script>
3. toRef:
toRef 是一个用于将响应式对象中的某个属性转换为响应式引用的函数。它的主要作用是在需要将某个响应式对象的属性传递给子组件或其他地方时,保持该属性的响应性
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const state = reactive({
count: 0,
})
// 使用 toRef
// `count` 是一个响应式引用,直接在模板中使用,它会随着 `state.count` 的变化而更新
const count = toRef(state, 'count')
function increment() {
state.count++
}
</script>
4. toRefs:
toRefs 是一个用于将响应式对象的所有属性转换为响应式引用的函数。它的主要作用是保持响应式对象中的所有属性在解构时的响应性。将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的
<template>
<div>
<p>消息: {{ message }}</p>
<button @click="changeMessage">更新</button>
</div>
</template>
<script setup lang="ts">
import { reactive, toRefs } from 'vue'
const state = reactive({
message: 'Hello, Vue 3 toRefs!',
})
// 解构时使用 toRefs
const { message } = toRefs(state)
function changeMessage() {
state.message = '更新内容!'
}
</script>
5. toRaw:
toRaw 是一个用于访问响应式对象原始值的函数。它的主要作用是获取一个响应式对象内部的原始对象,而不再是响应式的代理对象。根据一个 Vue 创建的代理返回其原始对象(官方原话 不建议保存对原始对象的持久引用,请谨慎使用)
<template>
<div>
<button @click="logRaw">查看原始数据</button>
</div>
</template>
<script setup lang="ts">
import { reactive, toRaw } from 'vue'
const state = reactive({
message: 'Hello, Vue 3 toRaw!',
})
function logRaw() {
const raw = toRaw(state)
// 输出 state 的原始值
console.log(raw)
}
</script>
6. toValue:
toValue 是一个用于获取响应式引用(如 ref)的原始值的函数。它的主要作用是从 ref 对象中提取出原始值,这样开发者可以更方便地访问和使用该值。将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。
<template>
<div>{{ rawCount }}</div>
</template>
<script setup lang="ts">
import { ref, toValue } from 'vue'
const count = ref(0)
// 使用 toValue 获取原始值
// rawCount 将保存 count 的原始值
// 在大多数情况下,直接访问 ref 对象的 .value 属性就足够了,因此 toValue 使用场景相对有限
// toValue 通常在需要更清晰或更简洁的代码时使用
const rawCount = toValue(count)
</script>
7. unref:
unref 是一个用于获取响应式引用(如 ref)的原始值的函数。与 toValue 类似,unref 使得在操作响应式引用时更加简洁和方便。如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="logValue">查看原始值</button>
</div>
</template>
<script setup lang="ts">
import { ref, unref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
function logValue() {
// 在一些情况下,直接访问 ref 对象的 .value 属性就足够了,因此 unref 使用场景也相对有限
const rawCount = unref(count)
console.log(rawCount)
}
</script>
8. shallowRef:
shallowRef 是一个用于创建浅响应引用的函数,它的主要作用是创建一个响应式引用,但不会对其内部的对象进行深度响应处理。换句话说,shallowRef 只会对引用本身的变化做出响应,不会对子对象或数组的变化进行自动追踪。(避免不必要的深度响应) ref() 的浅层作用形式
<template>
<div>
<p>名称: {{ obj.name }}</p>
<button @click="updateName">更新名称</button>
<button @click="updateObj">更新对象</button>
</div>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
const obj = shallowRef({
name: 'Vue 3',
version: '3.x',
})
function updateName() {
// 这里不会触发响应式更新,因为内部属性的变化不会被追踪
// 只是修改了对象内部的一个属性
obj.value.name = 'Updated Vue 3'
}
function updateObj() {
// 这将触发响应式更新,因为我们更改了引用
// 函数会创建一个新对象,替换原来的引用,从而触发视图更新
obj.value = { name: 'New Vue', version: '3.x' }
}
</script>
9. triggerRef:
triggerRef 是一个用于手动触发响应式引用(如 ref 或 shallowRef)更新的函数。它的主要作用是当你想要强制某个响应式引用的视图更新时,可以使用 triggerRef。强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="forceUpdate">强制更新</button>
</div>
</template>
<script setup lang="ts">
import { ref, triggerRef } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
function forceUpdate() {
// 手动触发视图更新 (强制视图更新)有点意思直接的对标 vue2的 $forceUpdate
// 使用 triggerRef 的需求一般较少,因为 Vue 的响应式系统通常能够自动处理大多数场景
// 在使用时要确保理解何时需要手动触发更新,以避免不必要的性能浪费
triggerRef(count)
}
</script>
10. customRef:
customRef 是一个用于创建自定义响应式引用的函数。它允许开发者通过定义自己的 getter 和 setter 来控制响应式引用的行为。这种方式使得你可以实现一些特定的逻辑,或对响应式数据的访问和更改进行更细致的管理。创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script setup lang="ts">
import { customRef } from 'vue'
// 自定义逻辑:当需要在获取和设置引用时执行一些额外的逻辑时,可以使用 customRef
// 性能优化:在某些情况下,你可能希望避免不必要的响应式更新,使用 customRef 可以帮助你实现这一点
// 封装复杂数据逻辑:当需要封装复杂的状态管理而不仅仅是简单的数据存储时,可以使用 customRef
// 使用 customRef 时,要确保调用 track 和 trigger 函数,以保证响应式系统的正常工作
const count = customRef((track, trigger) => {
let value = 0
return {
get() {
track() // 收集依赖
return value
},
set(newValue) {
value = newValue
trigger() // 触发更新
},
}
})
function increment() {
count.value++
}
function decrement() {
count.value--
}
</script>
11. computed:
computed 是一个用于创建计算属性的函数,它可以根据一个或多个响应式源值动态计算出一个值,并在依赖的值发生变化时自动更新。计算属性在模板中使用时,可以提高性能,因为它们会进行缓存,只有当依赖的值改变时,才会重新计算。接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
<template>
<div>
<p>计数器: {{ count }}</p>
<p>双倍计数: {{ doubledCount }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
// 计算属性是“惰性求值”的,只有在相关的依赖发生变化时才会重新计算,这有助于节省性能开销
// 计算属性一定要返回值,通常用于直接在模板中展示或作为逻辑处理的中间结果
const doubledCount = computed(() => {
return count.value * 2
})
function increment() {
count.value++
}
function decrement() {
count.value--
}
</script>
12. watch:
watch 是一个用于监听响应式数据变化的函数。它允许开发者对某个数据变化进行观察,并在数据变化时执行相应的回调函数。这对于需要在数据变化时执行一些副作用操作(例如 API 请求、数据处理等)非常有用。
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const count = ref(0)
// watch 可以监听多个响应式数据,支持数组形式的参数
// watch 还可以传入选项对象,来控制深度监听和立即执行等特性,通过 deep: true 开启深度监听
// 使用 immediate: true 时,会在创建时立即执行回调函数,监听首次渲染
watch(count, (newCount, oldCount) => {
console.log(`计数器从 ${oldCount} 变为 ${newCount}`)
})
// 监听多个数据 数组形式
watch([xxx, xxx], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
function increment() {
count.value++
}
function decrement() {
count.value--
}
</script>
13. watchEffect:
watchEffect 是一个用于执行副作用的函数。与 watch 不同的是,watchEffect 主要用于自动跟踪其内部的响应式依赖,能够简化副作用的处理。这使得它非常适合于简单的场景,尤其是在不需要明确指定哪些数据需要被监听的情况下。立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
const count = ref(0)
// 使用 watchEffect 监听响应式数据变化
// 自动响应依赖:当需要在某个函数中直接访问响应式数据并希望在这些数据变化时自动重新执行该函数时,使用 watchEffect
// 简化副作用函数:适用于需要简单监听和执行任务的场景,不需要指定依赖项,可以自动处理所有在函数中引用的响应式数据
// watchEffect 会在初始运行时立即执行一次,以提供当前状态
// 当内部使用了多个响应式数据时,watchEffect会自动监听这些数据,而不需要手动指定依赖
// 不同于 watch,watchEffect 的依赖关系是自动收集的,因此不能在其外部调用
watchEffect(() => {
console.log(`计数器值发生变化,当前值:${count.value}`)
})
function increment() {
count.value++
}
function decrement() {
count.value--
}
</script>
14. useAttrs:
useAttrs 是一个组合式 API 函数,用于获取当前组件的所有属性(props)。useAttrs 通常用于将未定义在组件内的属性转发给子组件,从而支持更好的灵活性和可扩展性。从 Setup 上下文中返回 attrs 对象,其中包含当前组件的透传 attributes。这是用于 <script setup> 中的,因为在 <script setup> 中无法获取 setup 上下文对象的。
<template>
<div v-bind="attrs">
<p>这是一个自定义组件,支持外部传入的属性</p>
</div>
</template>
<script setup lang="ts">
import { useAttrs } from 'vue'
// useAttrs 返回的对象是一个响应式对象,因此可以在模板和逻辑中直接使用
// 在使用 useAttrs 时,要注意处理传入的属性,防止意外的属性冲突
// 如果组件的 inheritAttrs 选项设置为 false,则需要手动将属性分配给子组件或元素
const attrs = useAttrs()
console.log(attrs, 'attrs')
</script>
15. useSlots:
useSlots 是一个组合式 API 函数,用于访问当前组件的插槽(slots)。这使得开发者可以动态地渲染传入组件的内容,从而实现更灵活的组件设计。从 Setup 上下文中返回 slots 对象,其中包含父组件传递的插槽。这些插槽为可调用的函数,返回虚拟 DOM 节点。这是用于 <script setup> 中的,因为在 <script setup> 中无法获取 setup 上下文对象的。
如果使用 TypeScript,建议优先使用 defineSlots()。
<template>
<div>
<h2>这是一个自定义组件</h2>
<div>
<!-- 动态渲染传入的插槽内容 -->
<slot name="header"></slot>
</div>
<div>
<slot></slot>
<!-- 默认插槽 -->
</div>
</div>
</template>
<script setup lang="ts">
import { useSlots } from 'vue'
// 传入一个具名插槽 header 和一个默认插槽 通过 slots 对象可以握住这些插槽,并在需要的地方动态渲染
// useSlots 返回的插槽对象是响应式的,因此可以在逻辑和模板中直接使用插槽内容
// 当使用插槽时,确保在父组件中定义了对应的插槽,才能在子组件中正确渲染内容
// 插槽的逻辑可以在 JavaScript 中进行更多的条件判断和处理,以实现更复杂的插槽使用场景
// 插槽的本质是函数(动态渲染、参数传递、响应式都支持)这个特性使得插槽可以灵活和动态地渲染内容,从而提高了组件的复用性和灵活性
const slots = useSlots()
console.log(slots, 'slots')
</script>
16. useModel:
useModel 是一个组合式 API 函数,用于简化模型绑定的处理。它主要用于处理组件中的双向绑定,尤其是在与表单输入元素交互时。useModel 使得创建自定义组件时,便于将模型值与组件的属性和事件结合起来。这是驱动 defineModel() 的底层辅助函数。如果使用 <script setup>,应当优先使用 defineModel() 仅在 3.4+ 版本中可用
// 子组件(自定义组件)
<template>
<div>
<input :value="modelValue" @input="updateValue" placeholder="请输入内容" />
</div>
</template>
<script setup lang="ts">
import { useModel } from 'vue';
const { modelValue, emit } = useModel();
// 更新模型值
function updateValue(event) {
const newValue = event.target.value;
emit('update:modelValue', newValue); // 触发更新事件
}
</script>
// 父组件
<template>
<CustomInput v-model="inputValue" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const inputValue = ref('');
</script>
17. useTemplateRef:
useTemplateRef 是一个组合式 API 函数,用于处理模板引用(template refs)。它允许你在 <script setup> 中获取和操作组件模板中定义的引用。这在需要直接操作某些 DOM 元素或子组件实例时非常有用。返回一个浅层 ref,其值将与模板中的具有匹配 ref attribute 的元素或组件同步。 仅在 3.5+ 版本中可用
<template>
<input ref="input" />
</template>
<script lang="ts" setup>
import { useTemplateRef, onMounted } from 'vue'
// 确保引用名称(如 `'input'`)与模板中被 `ref` 指令绑定的名称一致
// 在操作引用中的 DOM 元素时,务必确保组件已经渲染完成,以避免访问 `undefined` 或错误的引用
// `useTemplateRef` 主要用于 DOM 操作或自身组件实例的操作,对于 Vue 的响应式特性并不直接影响
const inputRef = useTemplateRef('input')
onMounted(() => {
inputRef.value.focus()
})
</script>
18. useId:
useId 是一个组合式 API 函数,用于生成唯一的 ID。这在需要生成唯一标识符的场景中非常实用,尤其是在处理具有动态内容的表单、组件或元素时。仅在 3.5+ 版本中可用
<template>
<div>
<label :for="id">输入框:</label>
<input :id="id" />
</div>
</template>
<script setup lang="ts">
import { useId } from 'vue';
// 生成唯一的 ID
// `useId` 在组件的每次实例化中都会生成一个新的唯一 ID,并且对于不同的组件实例是不同的
// 使用 `useId` 生成的 ID 不应该用于作为数据库主键或其他需要持久存储的唯一标识符,因为它们仅用于组件的生命周期内
const id = useId();
</script>
其他的 Composition API 参照Vue官网即可,这里列举的都是比较核心比较常用的。。。另外还有生命周期、全局api、h函数(这个大家都比较熟悉吧应该)、单文件组件相关的如
其他
就不一一列举了列举其中一种即可,其他都触类旁通吧;当然没提及的api有兴趣的同学可以自行挨个探讨以及研究研究~现在都更新到Vue3.5+ 了呢!!!