前端开发··24 阅读·预计 17 分钟

Vue3 组合式 API 实战:从入门到精通的完整指南

一、背景与动机

Vue3 的发布带来了革命性的变化,其中最引人注目的莫过于组合式 API(Composition API)。在 Vue2 时代,我们习惯使用选项式 API(Options API)来组织代码,但随着项目规模的增长,这种方式逐渐暴露出一些问题:逻辑复用困难、代码组织分散、TypeScript 支持不够友好等。

组合式 API 的出现正是为了解决这些痛点。它允许我们按照逻辑关注点组织代码,而不是按照选项类型分散在 data、methods、computed 等不同区域。这不仅提高了代码的可读性和可维护性,还为逻辑复用提供了更优雅的解决方案。

二、核心概念详解

2.1 响应式系统

组合式 API 的核心是响应式系统,主要由 refreactive 两个函数构成。

ref 的使用:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello Vue3')
    
    // 在 setup 中需要 .value 访问
    console.log(count.value) // 0
    
    // 在模板中自动解包,无需 .value
    return { count, message }
  }
}

reactive 的使用:

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: {
        name: '张三',
        age: 25,
        skills: ['Vue', 'React', 'TypeScript']
      },
      loading: false,
      error: null
    })
    
    // 直接访问属性,无需 .value
    console.log(state.user.name) // 张三
    
    return { state }
  }
}

2.2 setup 函数

setup 是组合式 API 的入口点,在组件创建之前执行,是定义响应式状态、计算属性、方法和生命周期钩子的地方。

<script setup>
import { ref, computed, onMounted } from 'vue'

// 定义响应式状态
const count = ref(0)
const doubledCount = computed(() => count.value * 2)

// 定义方法
const increment = () => {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log('组件已挂载')
})
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Doubled: {{ doubledCount }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

2.3 生命周期钩子

组合式 API 提供了与选项式 API 对应的生命周期钩子函数:

选项式 API组合式 API
beforeCreatesetup() 本身
createdsetup() 本身
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted

三、实战案例:用户管理系统

下面通过一个完整的用户管理模块来展示组合式 API 的最佳实践。

3.1 逻辑复用:自定义 Composable

// composables/useUser.js
import { ref, computed } from 'vue'
import { fetchUsers, createUser, updateUser, deleteUser } from '@/api/user'

export function useUser() {
  const users = ref([])
  const loading = ref(false)
  const error = ref(null)
  const selectedUser = ref(null)
  
  // 计算属性
  const activeUsers = computed(() => 
    users.value.filter(user => user.status === 'active')
  )
  
  const userCount = computed(() => users.value.length)
  
  // 获取用户列表
  const loadUsers = async () => {
    loading.value = true
    error.value = null
    try {
      const response = await fetchUsers()
      users.value = response.data
    } catch (e) {
      error.value = e.message || '获取用户列表失败'
    } finally {
      loading.value = false
    }
  }
  
  // 创建用户
  const addUser = async (userData) => {
    loading.value = true
    try {
      const response = await createUser(userData)
      users.value.push(response.data)
      return { success: true, data: response.data }
    } catch (e) {
      error.value = e.message || '创建用户失败'
      return { success: false, error: error.value }
    } finally {
      loading.value = false
    }
  }
  
  // 更新用户
  const editUser = async (id, userData) => {
    loading.value = true
    try {
      const response = await updateUser(id, userData)
      const index = users.value.findIndex(u => u.id === id)
      if (index !== -1) {
        users.value[index] = response.data
      }
      return { success: true, data: response.data }
    } catch (e) {
      error.value = e.message || '更新用户失败'
      return { success: false, error: error.value }
    } finally {
      loading.value = false
    }
  }
  
  // 删除用户
  const removeUser = async (id) => {
    loading.value = true
    try {
      await deleteUser(id)
      users.value = users.value.filter(u => u.id !== id)
      return { success: true }
    } catch (e) {
      error.value = e.message || '删除用户失败'
      return { success: false, error: error.value }
    } finally {
      loading.value = false
    }
  }
  
  return {
    // 状态
    users,
    loading,
    error,
    selectedUser,
    // 计算属性
    activeUsers,
    userCount,
    // 方法
    loadUsers,
    addUser,
    editUser,
    removeUser
  }
}

3.2 组件中使用 Composable

<!-- components/UserManager.vue -->
<script setup>
import { onMounted } from 'vue'
import { useUser } from '@/composables/useUser'
import UserForm from './UserForm.vue'
import UserList from './UserList.vue'

const {
  users,
  loading,
  error,
  activeUsers,
  userCount,
  loadUsers,
  addUser,
  editUser,
  removeUser
} = useUser()

// 初始化加载
onMounted(() => {
  loadUsers()
})

// 处理表单提交
const handleSubmit = async (formData) => {
  const result = formData.id 
    ? await editUser(formData.id, formData)
    : await addUser(formData)
  
  if (result.success) {
    // 显示成功提示
    console.log('操作成功')
  }
}

// 处理删除
const handleDelete = async (id) => {
  if (confirm('确定要删除该用户吗?')) {
    const result = await removeUser(id)
    if (result.success) {
      console.log('删除成功')
    }
  }
}
</script>

<template>
  <div class="user-manager">
    <header>
      <h1>用户管理</h1>
      <p>总用户数: {{ userCount }} | 活跃用户: {{ activeUsers.length }}</p>
    </header>
    
    <UserForm @submit="handleSubmit" />
    
    <div v-if="loading" class="loading">加载中...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <UserList v-else :users="users" @edit="handleSubmit" @delete="handleDelete" />
  </div>
</template>

四、最佳实践总结

4.1 ref vs reactive 的选择

  • 使用 ref:当需要处理基本类型(string、number、boolean)或需要重新分配整个对象时
  • 使用 reactive:当处理复杂对象,且不需要重新分配整个对象时
  • 推荐做法:团队内保持一致性,建议优先使用 ref,因为解构时不会丢失响应性

4.2 Composable 命名与组织

  • use 开头命名,如 useUseruseCart
  • 单一职责:每个 Composable 只负责一个逻辑关注点
  • 返回值使用对象,便于按需解构
  • 将 Composable 放在 composables/hooks/ 目录下

4.3 性能优化技巧

// 使用 shallowRef 避免深度响应
import { shallowRef } from 'vue'
const largeData = shallowRef([])

// 使用 computed 缓存计算结果
const filteredList = computed(() => 
  list.value.filter(item => item.active)
)

// 使用 watchEffect 自动追踪依赖
watchEffect(() => {
  // 自动收集依赖,依赖变化时自动执行
  localStorage.setItem('theme', theme.value)
})

五、常见问题与解决方案

5.1 解构失去响应性

// ❌ 错误:解构后失去响应性
const { name, age } = reactive({ name: '张三', age: 25 })

// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const state = reactive({ name: '张三', age: 25 })
const { name, age } = toRefs(state)

5.2 在 setup 中访问组件实例

组合式 API 中没有 this,如果需要访问组件实例,可以使用 getCurrentInstance

import { getCurrentInstance } from 'vue'

export default {
  setup() {
    const instance = getCurrentInstance()
    // 仅在必要时使用,不推荐过度依赖
  }
}

5.3 响应式丢失排查

使用 isRefisReactive 工具函数进行调试:

import { isRef, isReactive, ref, reactive } from 'vue'

const count = ref(0)
const state = reactive({ name: 'test' })

console.log(isRef(count)) // true
console.log(isReactive(state)) // true

六、总结

Vue3 组合式 API 是一次重大的范式转变,它带来的好处包括:

  1. 更好的逻辑复用:通过 Composable 可以轻松提取和复用逻辑
  2. 更灵活的代码组织:按功能而非选项类型组织代码
  3. 更好的 TypeScript 支持:完整的类型推断
  4. 更小的打包体积:Tree-shaking 友好
  5. 更直观的代码阅读体验:相关代码聚合在一起

掌握组合式 API 是成为 Vue3 高手的必经之路。建议从小型组件开始尝试,逐步将选项式 API 的心智模型转换为组合式 API 的思维模式。相信随着实践的深入,你会发现组合式 API 的优雅和强大。

0 评论

评论区

登录 后参与评论