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

Edward826
Edward826 2026-01-26T21:13:20+08:00
0 0 1

前言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于 Vue 2 中的 Options API,Composition API 提供了更加灵活和强大的组件开发方式,使得代码组织更加清晰,逻辑复用更加容易。本文将深入探讨 Composition API 的核心概念、使用方法以及在实际项目中的应用实践。

什么是 Composition API

1.1 传统 Vue 2 Options API 的局限性

在 Vue 2 中,我们主要使用 Options API 来组织组件逻辑。这种方式虽然直观易懂,但在处理复杂组件时存在一些问题:

// Vue 2 Options API 示例
export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    reversedName() {
      return this.name.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}

这种写法的问题在于:

  • 相关逻辑分散在不同选项中
  • 难以将相关的逻辑组合在一起
  • 复杂组件难以维护和复用

1.2 Composition API 的优势

Composition API 通过 setup 函数将组件的所有逻辑集中管理,提供更灵活的代码组织方式:

// Vue 3 Composition API 示例
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    const reversedName = computed(() => name.value.split('').reverse().join(''))
    
    const increment = () => {
      count.value++
    }
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    return {
      count,
      name,
      reversedName,
      increment
    }
  }
}

基础语法详解

2.1 setup 函数

setup 是 Composition API 的入口函数,它在组件实例创建之前执行,接收两个参数:

import { ref, reactive } from 'vue'

export default {
  setup(props, context) {
    // props: 组件属性对象
    // context: 上下文对象,包含 emit、attrs 等
    
    const count = ref(0)
    
    return {
      count
    }
  }
}

2.2 响应式数据的创建

2.2.1 ref 的使用

ref 用于创建响应式的数据引用:

import { ref } from 'vue'

export default {
  setup() {
    // 创建基本类型响应式数据
    const count = ref(0)
    const message = ref('Hello Vue')
    const isActive = ref(true)
    
    // 修改值
    count.value = 10
    
    return {
      count,
      message,
      isActive
    }
  }
}

2.2.2 reactive 的使用

reactive 用于创建响应式对象:

import { reactive } from 'vue'

export default {
  setup() {
    // 创建响应式对象
    const state = reactive({
      count: 0,
      name: 'Vue',
      userInfo: {
        age: 25,
        email: 'vue@example.com'
      }
    })
    
    // 修改属性
    state.count = 10
    state.userInfo.age = 30
    
    return {
      state
    }
  }
}

2.3 计算属性

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    
    // 基本计算属性
    const fullName = computed(() => `${firstName.value} ${lastName.value}`)
    
    // 带 getter 和 setter 的计算属性
    const reversedName = computed({
      get: () => firstName.value.split('').reverse().join(''),
      set: (value) => {
        firstName.value = value.split('').reverse().join('')
      }
    })
    
    return {
      firstName,
      lastName,
      fullName,
      reversedName
    }
  }
}

2.4 监听器

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    // 基本监听器
    watch(count, (newVal, oldVal) => {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`)
    })
    
    // 监听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
    })
    
    // watchEffect 会自动追踪其内部使用的响应式数据
    watchEffect(() => {
      console.log(`当前 count 值为: ${count.value}`)
    })
    
    return {
      count,
      name
    }
  }
}

生命周期钩子

3.1 常用生命周期钩子

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

export default {
  setup() {
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    onBeforeUpdate(() => {
      console.log('组件即将更新')
    })
    
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
    
    onUnmounted(() => {
      console.log('组件已卸载')
    })
    
    return {}
  }
}

3.2 自定义生命周期钩子

import { onMounted, onUnmounted } from 'vue'

// 自定义 Hook:定时器管理
function useTimer(callback, interval) {
  let timer
  
  onMounted(() => {
    timer = setInterval(callback, interval)
  })
  
  onUnmounted(() => {
    if (timer) {
      clearInterval(timer)
    }
  })
  
  return {
    clearTimer: () => {
      if (timer) {
        clearInterval(timer)
        timer = null
      }
    }
  }
}

export default {
  setup() {
    const count = ref(0)
    
    const increment = () => {
      count.value++
    }
    
    // 使用自定义 Hook
    useTimer(increment, 1000)
    
    return {
      count,
      increment
    }
  }
}

状态管理实战

4.1 组件间状态共享

// composables/useSharedState.js
import { ref } from 'vue'

const sharedState = ref(null)

export function useSharedState() {
  const getState = () => sharedState.value
  
  const setState = (value) => {
    sharedState.value = value
  }
  
  const resetState = () => {
    sharedState.value = null
  }
  
  return {
    state: sharedState,
    getState,
    setState,
    resetState
  }
}

// 在组件中使用
import { useSharedState } from '@/composables/useSharedState'

export default {
  setup() {
    const { state, setState, resetState } = useSharedState()
    
    const updateSharedData = () => {
      setState({ message: 'Hello from component', timestamp: Date.now() })
    }
    
    return {
      state,
      updateSharedData,
      resetState
    }
  }
}

4.2 表单状态管理

// composables/useForm.js
import { reactive, computed } from 'vue'

export function useForm(initialData = {}) {
  const formData = reactive({ ...initialData })
  const errors = reactive({})
  
  // 验证规则
  const rules = {
    required: (value) => value !== null && value !== undefined && value !== '',
    email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    minLength: (value, min) => String(value).length >= min
  }
  
  // 验证字段
  const validateField = (field, rule, value) => {
    if (!rules[rule]) return true
    
    const isValid = rules[rule](value)
    
    if (!isValid) {
      errors[field] = `${field} 不符合 ${rule} 规则`
    } else {
      delete errors[field]
    }
    
    return isValid
  }
  
  // 验证整个表单
  const validateForm = () => {
    const formErrors = {}
    Object.keys(formData).forEach(field => {
      if (!formData[field]) {
        formErrors[field] = `${field} 是必填项`
      }
    })
    
    return Object.keys(formErrors).length === 0
  }
  
  // 重置表单
  const resetForm = () => {
    Object.keys(formData).forEach(key => {
      formData[key] = initialData[key] || ''
    })
    Object.keys(errors).forEach(key => {
      delete errors[key]
    })
  }
  
  const isFormValid = computed(() => {
    return Object.keys(errors).length === 0
  })
  
  return {
    formData,
    errors,
    validateField,
    validateForm,
    resetForm,
    isFormValid
  }
}

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

export default {
  setup() {
    const initialData = {
      name: '',
      email: '',
      age: ''
    }
    
    const { 
      formData, 
      errors, 
      validateField, 
      validateForm, 
      resetForm,
      isFormValid
    } = useForm(initialData)
    
    const handleSubmit = () => {
      if (validateForm()) {
        console.log('表单提交:', formData)
        // 处理提交逻辑
      }
    }
    
    return {
      formData,
      errors,
      validateField,
      handleSubmit,
      resetForm,
      isFormValid
    }
  }
}

组件设计模式

5.1 可复用的组件组合

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

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

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

export default {
  setup() {
    const { data, loading, error, fetchData } = useApi('/api/users')
    
    fetchData()
    
    return {
      users: data,
      loading,
      error
    }
  }
}

5.2 组件状态管理

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

export function useComponentState(initialState = {}) {
  const state = ref({ ...initialState })
  
  // 监听状态变化
  watch(state, (newState, oldState) => {
    console.log('组件状态变化:', { oldState, newState })
  }, { deep: true })
  
  const updateState = (key, value) => {
    state.value[key] = value
  }
  
  const updateMultiple = (updates) => {
    Object.assign(state.value, updates)
  }
  
  const resetState = () => {
    Object.assign(state.value, initialState)
  }
  
  return {
    state,
    updateState,
    updateMultiple,
    resetState
  }
}

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

export default {
  setup() {
    const { state, updateState, updateMultiple, resetState } = useComponentState({
      title: '默认标题',
      description: '默认描述',
      visible: true
    })
    
    const toggleVisibility = () => {
      updateState('visible', !state.value.visible)
    }
    
    return {
      ...state,
      toggleVisibility,
      resetState
    }
  }
}

5.3 自定义 Hook 设计

// 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)
  
  // 监听值变化并同步到 localStorage
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  const setValue = (newValue) => {
    value.value = newValue
  }
  
  const removeValue = () => {
    localStorage.removeItem(key)
    value.value = defaultValue
  }
  
  return {
    value,
    setValue,
    removeValue
  }
}

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

export default {
  setup() {
    const { value: theme, setValue: setTheme } = useLocalStorage('theme', 'light')
    const { value: userPreferences, setValue: setUserPreferences } = useLocalStorage('userPrefs', {})
    
    return {
      theme,
      setTheme,
      userPreferences,
      setUserPreferences
    }
  }
}

高级应用技巧

6.1 异步数据处理

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

export function useAsyncData(asyncFunction, dependencies = []) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const execute = async (...args) => {
    loading.value = true
    error.value = null
    
    try {
      const result = await asyncFunction(...args)
      data.value = result
    } catch (err) {
      error.value = err
      console.error('异步数据处理失败:', err)
    } finally {
      loading.value = false
    }
  }
  
  // 自动执行依赖变化时的数据加载
  if (dependencies.length > 0) {
    // 这里可以实现更复杂的依赖监听逻辑
  }
  
  return {
    data,
    loading,
    error,
    execute
  }
}

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

export default {
  setup() {
    const { data, loading, error, execute } = useAsyncData(
      async (userId) => {
        const response = await fetch(`/api/users/${userId}`)
        return response.json()
      },
      []
    )
    
    // 手动触发数据加载
    const loadUserData = () => execute(123)
    
    return {
      user: data,
      loading,
      error,
      loadUserData
    }
  }
}

6.2 条件渲染和动态组件

// composables/useDynamicComponent.js
import { ref, computed } from 'vue'

export function useDynamicComponent(components) {
  const currentComponent = ref(null)
  
  const setComponent = (componentName) => {
    if (components[componentName]) {
      currentComponent.value = components[componentName]
    }
  }
  
  const switchComponent = (from, to) => {
    if (components[from]) {
      currentComponent.value = components[to]
    }
  }
  
  return {
    currentComponent,
    setComponent,
    switchComponent
  }
}

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

export default {
  setup() {
    const components = {
      login: () => import('@/components/Login.vue'),
      register: () => import('@/components/Register.vue'),
      profile: () => import('@/components/Profile.vue')
    }
    
    const { currentComponent, setComponent } = useDynamicComponent(components)
    
    return {
      currentComponent,
      switchToLogin: () => setComponent('login'),
      switchToRegister: () => setComponent('register'),
      switchToProfile: () => setComponent('profile')
    }
  }
}

6.3 性能优化策略

// composables/useMemo.js
import { ref, computed } from 'vue'

export function useMemo(computation, dependencies) {
  const cache = ref(null)
  const isCached = ref(false)
  
  const result = computed(() => {
    if (!isCached.value || dependencies.some(dep => dep.value !== undefined)) {
      cache.value = computation()
      isCached.value = true
    }
    return cache.value
  })
  
  const invalidate = () => {
    isCached.value = false
  }
  
  return {
    result,
    invalidate
  }
}

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

export default {
  setup() {
    const count = ref(0)
    const multiplier = ref(2)
    
    const expensiveCalculation = () => {
      console.log('执行昂贵计算')
      return Array.from({ length: count.value }, (_, i) => i * multiplier.value)
    }
    
    const { result, invalidate } = useMemo(expensiveCalculation, [count, multiplier])
    
    const incrementCount = () => {
      count.value++
      invalidate() // 当依赖变化时重新计算
    }
    
    return {
      count,
      multiplier,
      result,
      incrementCount
    }
  }
}

最佳实践和注意事项

7.1 代码组织规范

// 组件结构最佳实践
import { 
  ref, 
  reactive, 
  computed, 
  watch, 
  onMounted, 
  onUnmounted 
} from 'vue'

// 1. 响应式数据声明
const state = reactive({
  // 状态数据
})

const count = ref(0)
const name = ref('')

// 2. 计算属性
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
const isReady = computed(() => count.value > 0)

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

const reset = () => {
  count.value = 0
}

// 4. 生命周期钩子
onMounted(() => {
  // 组件挂载时的逻辑
})

onUnmounted(() => {
  // 组件卸载时的清理工作
})

// 5. 监听器
watch(count, (newVal, oldVal) => {
  console.log(`count 变化: ${oldVal} -> ${newVal}`)
})

// 6. 返回值
return {
  state,
  count,
  name,
  fullName,
  isReady,
  increment,
  reset
}

7.2 错误处理和调试

// composables/useErrorHandler.js
import { ref } from 'vue'

export function useErrorHandler() {
  const error = ref(null)
  
  const handleError = (errorObj, context = '') => {
    console.error(`[Error] ${context}:`, errorObj)
    
    if (errorObj instanceof Error) {
      error.value = {
        message: errorObj.message,
        stack: errorObj.stack,
        timestamp: new Date().toISOString()
      }
    } else {
      error.value = {
        message: String(errorObj),
        timestamp: new Date().toISOString()
      }
    }
    
    // 可以在这里添加错误上报逻辑
    reportError(error.value)
  }
  
  const clearError = () => {
    error.value = null
  }
  
  const reportError = (errorInfo) => {
    // 上报错误到监控系统
    console.log('错误上报:', errorInfo)
  }
  
  return {
    error,
    handleError,
    clearError
  }
}

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

export default {
  setup() {
    const { error, handleError, clearError } = useErrorHandler()
    
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data')
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`)
        }
        return response.json()
      } catch (err) {
        handleError(err, '数据获取失败')
        throw err
      }
    }
    
    return {
      error,
      fetchData,
      clearError
    }
  }
}

7.3 测试友好性

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => {
    count.value++
  }
  
  const decrement = () => {
    count.value--
  }
  
  const reset = () => {
    count.value = initialValue
  }
  
  const double = computed(() => count.value * 2)
  
  return {
    count,
    increment,
    decrement,
    reset,
    double
  }
}

// 测试示例 (Jest)
/*
describe('useCounter', () => {
  test('should initialize with correct value', () => {
    const { count } = useCounter(5)
    expect(count.value).toBe(5)
  })
  
  test('should increment correctly', () => {
    const { count, increment } = useCounter()
    increment()
    expect(count.value).toBe(1)
  })
  
  test('should decrement correctly', () => {
    const { count, decrement } = useCounter(5)
    decrement()
    expect(count.value).toBe(4)
  })
})
*/

总结

Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅解决了传统 Options API 的局限性,还提供了更加灵活和强大的组件开发能力。通过本文的介绍,我们可以看到:

  1. 更好的代码组织:将相关的逻辑集中管理,提高代码可读性和维护性
  2. 更强的复用能力:通过自定义 Hook 实现逻辑复用,减少重复代码
  3. 更清晰的状态管理:响应式数据的创建和管理更加直观
  4. 更好的性能优化:结合 computed、watch 等特性实现高效的性能控制

在实际项目中,建议根据具体需求选择合适的 API 风格。对于简单的组件,Options API 依然适用;而对于复杂的业务逻辑,Composition API 能够提供更优雅的解决方案。同时,合理使用自定义 Hook 可以大大提高代码的复用性和可维护性。

随着 Vue 生态的不断发展,Composition API 将会成为前端开发的重要工具,掌握它对于提升开发效率和代码质量具有重要意义。希望本文能够帮助开发者更好地理解和应用 Vue 3 Composition API,在实际项目中发挥其最大价值。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000