Vue3组合式API核心特性与实战指南
1. Vue3组合式API核心价值解析
作为Vue3最标志性的特性,组合式API彻底改变了我们编写Vue组件的方式。在传统选项式API中,我们需要将代码分散到data、methods、computed等不同选项中,而组合式API允许我们像编写普通JavaScript函数一样组织组件逻辑。这种范式转换带来了几个显著优势:
- 逻辑关注点聚合:相关功能的代码可以集中编写,不再需要在不同选项间跳转
- 类型推导友好:基于函数的API天然适配TypeScript类型系统
- 复用性提升:逻辑可以提取为独立函数,多个组件间轻松共享
- 灵活性增强:不受选项限制,可以自由组织代码结构
2. 十大核心组合式API详解
2.1 ref() - 响应式数据基础
import { ref } from 'vue' const count = ref(0) console.log(count.value) // 0 count.value++ref是创建响应式数据最基础的API,它接收一个内部值并返回一个响应式的、可变的ref对象。这个对象只有一个.value属性指向内部值。
注意:在模板中使用ref时不需要.value,Vue会自动解包。但在JavaScript中操作时必须通过.value访问。
典型应用场景:
- 基本类型数据的响应式处理
- 需要明确跟踪变化的独立值
- 需要传递给函数时保持响应性
2.2 reactive() - 复杂对象响应化
import { reactive } from 'vue' const state = reactive({ count: 0, user: { name: 'John' } }) state.count++reactive返回一个对象的响应式代理,它会深度转换所有属性。与ref不同,它直接操作对象属性而不需要.value。
与ref的主要区别:
- 适用于对象类型
- 不需要.value访问
- 解构会丢失响应性
- 不能直接替换整个对象
2.3 computed() - 派生状态管理
import { ref, computed } from 'vue' const count = ref(0) const doubleCount = computed(() => count.value * 2)computed接收一个getter函数,返回一个不可变的响应式ref对象。只有当依赖变化时才会重新计算。
性能优化技巧:
- 避免在计算属性中执行耗时操作
- 对于复杂计算考虑使用
computed的get/set形式 - 多个组件共享的计算属性可以提取到单独文件
2.4 watch() - 响应式侦听器
import { ref, watch } from 'vue' const count = ref(0) watch(count, (newVal, oldVal) => { console.log(`count changed from ${oldVal} to ${newVal}`) })watch可以侦听一个或多个响应式数据源,并在数据源变化时执行回调函数。
高级用法:
// 侦听多个源 watch([fooRef, barRef], ([foo, bar]) => { /* ... */ }) // 深度侦听对象 watch( () => state.someObject, (newVal) => { /* ... */ }, { deep: true } )2.5 watchEffect() - 自动依赖收集
import { ref, watchEffect } from 'vue' const count = ref(0) watchEffect(() => { console.log(`count is: ${count.value}`) })watchEffect会立即执行传入的函数,并自动追踪其依赖。当依赖变化时,会重新运行该函数。
与watch的区别:
- 自动收集依赖,不需要显式指定侦听源
- 立即执行,不需要设置immediate选项
- 无法获取变化前的值
2.6 onMounted() - 生命周期钩子
import { onMounted } from 'vue' onMounted(() => { console.log('组件已挂载') })组合式API提供了一系列生命周期钩子函数,包括:
onBeforeMount/onMountedonBeforeUpdate/onUpdatedonBeforeUnmount/onUnmountedonErrorCaptured
最佳实践:
- 将相关逻辑与生命周期钩子放在一起
- 避免在生命周期钩子中执行阻塞操作
- 清理副作用使用
onUnmounted
2.7 provide/inject - 跨组件通信
// 祖先组件 import { provide, ref } from 'vue' const count = ref(0) provide('count', count) // 后代组件 import { inject } from 'vue' const count = inject('count')provide和inject可以实现跨层级组件通信,避免prop逐层传递。
类型安全技巧:
// 定义注入的key import type { InjectionKey } from 'vue' const countKey = Symbol() as InjectionKey<number> provide(countKey, 0) const count = inject(countKey)2.8 useAttrs() - 属性访问
import { useAttrs } from 'vue' const attrs = useAttrs() console.log(attrs.class)useAttrs返回一个包含组件所有透传attribute的对象,包括未在props中声明的属性。
典型使用场景:
- 高阶组件封装
- 需要手动处理DOM属性的组件
- 需要透传属性到深层子组件
2.9 useSlots() - 插槽访问
import { useSlots } from 'vue' const slots = useSlots() console.log(slots.default)useSlots返回一个包含所有插槽的对象,可以用于动态渲染插槽内容。
高级用法:
// 条件渲染插槽 const slots = useSlots() if (slots.footer) { return h('div', slots.footer()) }2.10 defineProps/defineEmits - 类型安全声明
<script setup> // 运行时声明 const props = defineProps({ title: String }) // 基于类型的声明 const emit = defineEmits<{ (e: 'update', value: number): void }>() </script>defineProps和defineEmits是<script setup>中用于声明props和emits的编译器宏。
TypeScript集成:
interface Props { title?: string count: number } const props = defineProps<Props>()3. 组合式API最佳实践
3.1 逻辑提取与复用
将相关逻辑提取为组合式函数是保持代码整洁的关键:
// useCounter.js import { ref } from 'vue' export function useCounter(initialValue = 0) { const count = ref(initialValue) function increment() { count.value++ } return { count, increment } } // 组件中使用 import { useCounter } from './useCounter' const { count, increment } = useCounter()3.2 组织大型组件
对于复杂组件,可以按功能划分代码块:
<script setup> // 用户数据相关 const { user, updateUser } = useUser() // 表单验证相关 const { errors, validate } = useFormValidation() // API请求相关 const { loading, submit } = useSubmitHandler() </script>3.3 性能优化技巧
- 避免不必要的响应式:静态数据不需要响应式处理
- 合理使用shallowRef/shallowReactive:当不需要深度响应时
- 批量更新:使用
nextTick合并DOM更新 - 大型列表使用虚拟滚动:如
vue-virtual-scroller
4. 常见问题与解决方案
4.1 响应式丢失问题
问题:解构reactive对象会丢失响应性
const state = reactive({ count: 0 }) let { count } = state // 失去响应性解决方案:
- 使用
toRefs转换:
const state = reactive({ count: 0 }) let { count } = toRefs(state) // 保持响应性- 直接通过state访问
4.2 循环依赖问题
问题:组合式函数相互调用可能导致无限循环
解决方案:
- 重构代码结构,避免循环依赖
- 使用
markRaw标记不需要响应式的对象 - 考虑使用事件总线或状态管理库
4.3 内存泄漏问题
问题:未清理的订阅和定时器可能导致内存泄漏
解决方案:
import { onUnmounted } from 'vue' const timer = setInterval(() => {}, 1000) onUnmounted(() => clearInterval(timer))5. 组合式API与选项式API对比
| 特性 | 组合式API | 选项式API |
|---|---|---|
| 代码组织 | 按逻辑功能组织 | 按选项类型组织 |
| 类型支持 | 优秀 | 一般 |
| 学习曲线 | 较高 | 较低 |
| 逻辑复用 | 灵活 | 依赖mixins |
| 小型项目适用性 | 可以但可能过度 | 非常适合 |
| 大型项目适用性 | 非常适合 | 可能难以维护 |
| 响应式系统访问 | 直接 | 通过this间接访问 |
在实际项目中,两种API可以共存。Vue3完全支持在选项式API组件中使用setup()选项来逐步迁移到组合式API。
