Vue 3 Composition API实战指南:从基础语法到复杂组件设计

Max644
Max644 2026-01-27T15:15:05+08:00
0 0 1

前言

Vue 3 的发布带来了全新的 Composition API,这不仅是对 Vue 2.x 选项式 API 的补充,更是一次革命性的重构。Composition API 通过函数式的方式组织代码逻辑,让开发者能够更好地复用和管理组件状态,特别是在处理复杂组件时展现出强大的优势。

本文将深入探讨 Composition API 的核心概念、使用方法和最佳实践,从基础语法到高级特性,帮助开发者全面掌握现代 Vue 开发模式,提升组件的复用性和可维护性。

一、Composition API 核心概念

1.1 什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。与传统的选项式 API(Options API)不同,Composition API 允许我们使用函数来组织和复用组件逻辑,而不是将逻辑分散在各种选项中。

// Vue 2 Options API 示例
export default {
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2
    }
  }
}
// Vue 3 Composition API 示例
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    const doubledCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      message,
      doubledCount,
      increment
    }
  }
}

1.2 Composition API 的优势

Composition API 主要解决了以下几个问题:

  1. 逻辑复用:通过函数组合,可以轻松地在多个组件间共享逻辑
  2. 更好的代码组织:将相关的逻辑组织在一起,而不是分散在不同选项中
  3. 更灵活的开发体验:可以按功能模块来组织代码,提高可读性
  4. 类型支持更好:在 TypeScript 中有更好的类型推断支持

二、响应式 API 基础

2.1 ref 和 reactive 的基本使用

import { ref, reactive } from 'vue'

export default {
  setup() {
    // 基本数据类型
    const count = ref(0)
    const name = ref('Vue')
    
    // 对象类型
    const user = reactive({
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    })
    
    // 修改值
    const increment = () => {
      count.value++
    }
    
    const updateName = () => {
      name.value = 'Vue 3'
    }
    
    const updateUser = () => {
      user.age = 31
    }
    
    return {
      count,
      name,
      user,
      increment,
      updateName,
      updateUser
    }
  }
}

2.2 深层嵌套对象的响应式处理

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: {
        profile: {
          name: 'Alice',
          settings: {
            theme: 'dark',
            notifications: true
          }
        }
      }
    })
    
    // 直接修改深层属性
    const updateTheme = () => {
      state.user.profile.settings.theme = 'light'
    }
    
    return {
      state,
      updateTheme
    }
  }
}

2.3 toRef 和 toRefs 的使用

import { reactive, toRef, toRefs } from 'vue'

export default {
  setup() {
    const state = reactive({
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    })
    
    // 转换为 ref
    const firstNameRef = toRef(state, 'firstName')
    
    // 转换为多个 ref
    const { firstName, lastName, age } = toRefs(state)
    
    return {
      firstNameRef,
      firstName,
      lastName,
      age
    }
  }
}

三、生命周期钩子

3.1 生命周期钩子的使用方式

import { onMounted, onUpdated, onUnmounted, onBeforeMount } from 'vue'

export default {
  setup() {
    // 组件挂载前
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    // 组件挂载后
    onMounted(() => {
      console.log('组件已挂载')
      // 可以在这里执行 DOM 操作
    })
    
    // 组件更新后
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    // 组件卸载前
    onUnmounted(() => {
      console.log('组件即将卸载')
    })
    
    return {}
  }
}

3.2 在组合函数中使用生命周期

import { ref, onMounted, onUnmounted } from 'vue'

// 自定义组合函数
function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value++
  }
  
  const decrement = () => {
    count.value--
  }
  
  // 在组合函数中使用生命周期钩子
  onMounted(() => {
    console.log('计数器已挂载')
  })
  
  return {
    count,
    increment,
    decrement
  }
}

export default {
  setup() {
    const { count, increment, decrement } = useCounter(10)
    
    return {
      count,
      increment,
      decrement
    }
  }
}

四、计算属性和监听器

4.1 computed 的使用

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    const age = ref(30)
    
    // 基础计算属性
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // 带 setter 的计算属性
    const reversedName = computed({
      get: () => {
        return firstName.value.split('').reverse().join('')
      },
      set: (value) => {
        firstName.value = value.split('').reverse().join('')
      }
    })
    
    // 复杂计算
    const userStatus = computed(() => {
      if (age.value < 18) return '未成年'
      if (age.value < 60) return '成年人'
      return '老年人'
    })
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      reversedName,
      userStatus
    }
  }
}

4.2 watch 的使用

import { ref, watch, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    const user = ref({ name: 'John', age: 30 })
    
    // 监听单个 ref
    watch(count, (newValue, oldValue) => {
      console.log(`count 从 ${oldValue} 变为 ${newValue}`)
    })
    
    // 监听多个值
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
    })
    
    // 深度监听对象
    watch(user, (newValue, oldValue) => {
      console.log('用户信息发生变化')
    }, { deep: true })
    
    // 立即执行的监听器
    watch(count, (newValue) => {
      console.log(`立即执行: ${newValue}`)
    }, { immediate: true })
    
    // watchEffect - 自动追踪依赖
    const watchEffectExample = watchEffect(() => {
      console.log(`当前计数: ${count.value}`)
      console.log(`当前名称: ${name.value}`)
    })
    
    return {
      count,
      name,
      user,
      watchEffectExample
    }
  }
}

五、组件通信

5.1 Props 的传递和验证

import { ref } from 'vue'

export default {
  // 定义 props
  props: {
    title: {
      type: String,
      required: true,
      default: '默认标题'
    },
    count: {
      type: Number,
      default: 0,
      validator: (value) => value >= 0
    },
    user: {
      type: Object,
      default: () => ({})
    }
  },
  
  setup(props, { emit }) {
    const handleClick = () => {
      // 触发事件
      emit('click', props.count)
    }
    
    return {
      handleClick
    }
  }
}

5.2 emits 的使用

import { ref } from 'vue'

export default {
  emits: ['update:count', 'delete-item', 'custom-event'],
  
  setup(props, { emit }) {
    const count = ref(0)
    
    const increment = () => {
      count.value++
      // 触发事件
      emit('update:count', count.value)
    }
    
    const deleteItem = (id) => {
      emit('delete-item', id)
    }
    
    const customEvent = () => {
      emit('custom-event', { message: 'Hello' })
    }
    
    return {
      count,
      increment,
      deleteItem,
      customEvent
    }
  }
}

六、自定义组合函数

6.1 创建可复用的逻辑

// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
  
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return value
}

// 使用示例
import { useLocalStorage } from '@/composables/useLocalStorage'

export default {
  setup() {
    const theme = useLocalStorage('theme', 'light')
    const preferences = useLocalStorage('preferences', {})
    
    return {
      theme,
      preferences
    }
  }
}

6.2 复杂的组合函数示例

// composables/useApi.js
import { ref, reactive } from 'vue'

export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchData = async () => {
    try {
      loading.value = true
      error.value = null
      
      const response = await fetch(url)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  const refresh = () => {
    fetchData()
  }
  
  return {
    data,
    loading,
    error,
    refresh
  }
}

// 使用示例
import { useApi } from '@/composables/useApi'

export default {
  setup() {
    const { data, loading, error, refresh } = useApi('/api/users')
    
    // 在组件挂载时获取数据
    onMounted(() => {
      refresh()
    })
    
    return {
      data,
      loading,
      error,
      refresh
    }
  }
}

七、高级特性与最佳实践

7.1 条件渲染和动态组件

import { ref, computed } from 'vue'

export default {
  setup() {
    const currentView = ref('home')
    
    // 动态组件
    const dynamicComponent = computed(() => {
      switch (currentView.value) {
        case 'home':
          return 'HomeComponent'
        case 'about':
          return 'AboutComponent'
        case 'contact':
          return 'ContactComponent'
        default:
          return 'HomeComponent'
      }
    })
    
    const switchView = (view) => {
      currentView.value = view
    }
    
    return {
      currentView,
      dynamicComponent,
      switchView
    }
  }
}

7.2 异步组件和动态导入

import { defineAsyncComponent } from 'vue'

export default {
  setup() {
    // 异步组件
    const AsyncComponent = defineAsyncComponent(() => 
      import('./components/AsyncComponent.vue')
    )
    
    return {
      AsyncComponent
    }
  }
}

7.3 错误边界和异常处理

import { ref, onErrorCaptured } from 'vue'

export default {
  setup() {
    const error = ref(null)
    const errorInfo = ref(null)
    
    // 捕获错误
    onErrorCaptured((err, instance, info) => {
      error.value = err
      errorInfo.value = info
      console.error('捕获到错误:', err, info)
      
      // 返回 false 阻止错误继续向上传播
      return false
    })
    
    const throwError = () => {
      throw new Error('这是一个测试错误')
    }
    
    return {
      error,
      errorInfo,
      throwError
    }
  }
}

八、性能优化技巧

8.1 使用 computed 缓存

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([])
    
    // 复杂计算使用 computed 缓存
    const expensiveCalculation = computed(() => {
      // 模拟耗时操作
      console.log('执行复杂计算')
      return items.value.reduce((sum, item) => sum + item.value, 0)
    })
    
    const addItem = (value) => {
      items.value.push({ value })
    }
    
    return {
      items,
      expensiveCalculation,
      addItem
    }
  }
}

8.2 避免不必要的重新计算

import { ref, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    // 使用 computed 优化性能
    const expensiveOperation = computed(() => {
      // 只有当 count 改变时才重新计算
      return Math.pow(count.value, 2) + count.value * 10
    })
    
    // 监听特定值的变化
    watch(count, (newCount) => {
      console.log(`计数器更新为: ${newCount}`)
    })
    
    // 使用 watchEffect 避免重复计算
    const computedValue = ref(0)
    watchEffect(() => {
      // 只有当依赖的值发生变化时才执行
      computedValue.value = count.value * 2 + name.value.length
    })
    
    return {
      count,
      name,
      expensiveOperation,
      computedValue
    }
  }
}

九、与 Vue 2 的兼容性

9.1 迁移指南

// Vue 2 选项式 API
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}

// Vue 3 Composition API 等价写法
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    const doubledCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    return {
      count,
      doubledCount,
      increment
    }
  }
}

9.2 混合使用模式

import { ref, reactive } from 'vue'

export default {
  // 选项式 API 部分
  name: 'MixedComponent',
  
  // Composition API 部分
  setup() {
    const count = ref(0)
    
    return {
      count,
      increment: () => count.value++
    }
  },
  
  // 选项式 API 部分
  data() {
    return {
      message: 'Hello'
    }
  }
}

十、实际项目应用案例

10.1 用户管理组件

<template>
  <div class="user-management">
    <div class="controls">
      <input v-model="searchTerm" placeholder="搜索用户..." />
      <button @click="loadUsers">刷新</button>
    </div>
    
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <ul>
        <li v-for="user in filteredUsers" :key="user.id">
          {{ user.name }} - {{ user.email }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue'
import { useApi } from '@/composables/useApi'

export default {
  setup() {
    const searchTerm = ref('')
    const users = ref([])
    
    const { data, loading, error, refresh } = useApi('/api/users')
    
    // 过滤用户
    const filteredUsers = computed(() => {
      if (!searchTerm.value) return users.value
      
      return users.value.filter(user => 
        user.name.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
        user.email.toLowerCase().includes(searchTerm.value.toLowerCase())
      )
    })
    
    const loadUsers = () => {
      refresh()
    }
    
    // 监听数据变化
    onMounted(() => {
      if (data.value) {
        users.value = data.value
      }
    })
    
    return {
      searchTerm,
      filteredUsers,
      loading,
      error,
      loadUsers
    }
  }
}
</script>

10.2 表单验证组件

<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <label>用户名:</label>
      <input v-model="formData.username" />
      <span v-if="errors.username" class="error">{{ errors.username }}</span>
    </div>
    
    <div class="form-group">
      <label>邮箱:</label>
      <input v-model="formData.email" type="email" />
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>
    
    <button type="submit" :disabled="!isFormValid">提交</button>
  </form>
</template>

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

export default {
  setup() {
    const formData = reactive({
      username: '',
      email: ''
    })
    
    const errors = reactive({})
    
    // 验证规则
    const validateField = (field, value) => {
      switch (field) {
        case 'username':
          if (!value) return '用户名不能为空'
          if (value.length < 3) return '用户名至少3个字符'
          break
        case 'email':
          if (!value) return '邮箱不能为空'
          if (!/\S+@\S+\.\S+/.test(value)) return '邮箱格式不正确'
          break
      }
      return ''
    }
    
    // 实时验证
    const validateForm = () => {
      Object.keys(formData).forEach(field => {
        errors[field] = validateField(field, formData[field])
      })
    }
    
    // 计算表单有效性
    const isFormValid = computed(() => {
      return !Object.values(errors).some(error => error)
    })
    
    // 监听表单变化
    Object.keys(formData).forEach(field => {
      const fieldRef = ref(formData[field])
      fieldRef.value = formData[field]
      
      // 使用 watch 监听变化并验证
      watch(fieldRef, (newValue) => {
        formData[field] = newValue
        errors[field] = validateField(field, newValue)
      })
    })
    
    const handleSubmit = () => {
      if (isFormValid.value) {
        console.log('表单提交:', formData)
        // 处理表单提交逻辑
      }
    }
    
    return {
      formData,
      errors,
      isFormValid,
      handleSubmit
    }
  }
}
</script>

结语

Vue 3 的 Composition API 为前端开发带来了全新的可能性。通过本文的详细介绍,我们看到了 Composition API 在代码组织、逻辑复用、组件设计等方面的强大优势。

掌握 Composition API 不仅能帮助我们编写更清晰、更可维护的代码,还能显著提升开发效率。在实际项目中,合理运用组合函数、生命周期钩子和响应式 API,能够让我们构建出更加优雅和高效的 Vue 应用。

随着 Vue 生态的不断发展,Composition API 将会成为现代 Vue 开发的标准实践。建议开发者深入学习并熟练掌握这些技术,以适应前端技术的发展趋势,为用户创造更好的产品体验。

记住,好的代码不仅仅是功能的实现,更是可读性、可维护性和可扩展性的完美结合。Composition API 正是帮助我们实现这一目标的重要工具。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000