Vue 3 Composition API实战:从基础语法到复杂组件状态管理的最佳实践

StrongWizard
StrongWizard 2026-01-28T19:16:01+08:00
0 0 1

前言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性不仅解决了 Vue 2 中 Options API 的局限性,还为开发者提供了更加灵活和强大的组件开发方式。本文将深入探讨 Composition API 的核心概念、使用技巧,并结合实际项目场景演示如何进行复杂组件的状态管理。

什么是 Composition API

基础概念

Composition API 是 Vue 3 提供的一种新的组件状态管理方式,它允许我们以函数的形式组织和复用逻辑代码。与传统的 Options API(选项式 API)不同,Composition API 将组件的逻辑按功能模块进行拆分,使得代码更加清晰、可维护性更强。

在 Vue 2 中,我们通常使用 datamethodscomputedwatch 等选项来组织组件逻辑。而 Composition API 则将这些逻辑封装成独立的函数,通过 setup 函数进行统一管理。

与 Options API 的对比

// Vue 2 Options API
export default {
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    }
  }
}
// Vue 3 Composition API
import { ref, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    const doubledCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    return {
      count,
      message,
      doubledCount,
      increment
    }
  }
}

Composition API 核心特性详解

1. 响应式数据处理

ref 和 reactive 的使用

在 Composition API 中,refreactive 是两个核心的响应式工具函数。

import { ref, reactive } from 'vue'

export default {
  setup() {
    // 使用 ref 创建响应式数据
    const count = ref(0)
    const name = ref('Vue')
    
    // 使用 reactive 创建响应式对象
    const user = reactive({
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    })
    
    // 修改数据
    const increment = () => {
      count.value++ // 注意:访问时需要 .value
    }
    
    const updateName = () => {
      name.value = 'Vue 3'
    }
    
    return {
      count,
      name,
      user,
      increment,
      updateName
    }
  }
}

深度响应式与浅响应式

import { reactive, ref } from 'vue'

export default {
  setup() {
    // 深度响应式对象
    const deepObj = reactive({
      user: {
        profile: {
          name: 'Alice',
          age: 25
        }
      }
    })
    
    // 浅响应式对象
    const shallowObj = ref({
      user: {
        profile: {
          name: 'Bob',
          age: 30
        }
      }
    })
    
    // 深度响应式会监听所有嵌套属性的变化
    const updateDeepName = () => {
      deepObj.user.profile.name = 'Alice Updated'
    }
    
    // 浅响应式只监听顶层属性变化,嵌套对象需要手动处理
    const updateShallowName = () => {
      shallowObj.value.user.profile.name = 'Bob Updated'
    }
    
    return {
      deepObj,
      shallowObj,
      updateDeepName,
      updateShallowName
    }
  }
}

2. 计算属性和监听器

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}`
    })
    
    // 带 getter 和 setter 的计算属性
    const displayName = computed({
      get: () => {
        return `${firstName.value} ${lastName.value}`
      },
      set: (newValue) => {
        const names = newValue.split(' ')
        firstName.value = names[0]
        lastName.value = names[1]
      }
    })
    
    // 基于多个响应式数据的计算属性
    const isAdult = computed(() => {
      return age.value >= 18
    })
    
    const updateAge = (newAge) => {
      age.value = newAge
    }
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      displayName,
      isAdult,
      updateAge
    }
  }
}

watch 监听器

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    const user = reactive({
      profile: {
        name: 'John',
        age: 30
      }
    })
    
    // 基础监听器
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    // 监听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
    })
    
    // 深度监听对象
    watch(user, (newVal, oldVal) => {
      console.log('user changed:', newVal)
    }, { deep: true })
    
    // 监听器选项
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    }, {
      immediate: true,  // 立即执行
      deep: false       // 是否深度监听
    })
    
    // watchEffect 自动追踪依赖
    const watchEffectExample = () => {
      watchEffect(() => {
        console.log(`name: ${name.value}, count: ${count.value}`)
        // 这里会自动追踪 name 和 count 的变化
      })
    }
    
    return {
      count,
      name,
      user,
      watchEffectExample
    }
  }
}

生命周期钩子

setup 函数中的生命周期

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

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    // 挂载前
    console.log('setup executed')
    
    // 组件挂载后
    onMounted(() => {
      console.log('Component mounted')
      // 可以在这里执行 DOM 操作
      document.title = 'My Vue App'
    })
    
    // 组件更新后
    onUpdated(() => {
      console.log('Component updated')
    })
    
    // 组件卸载前
    onUnmounted(() => {
      console.log('Component will unmount')
      // 清理定时器、事件监听等
    })
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      message,
      increment
    }
  }
}

完整的生命周期管理

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

export default {
  setup() {
    const data = ref(null)
    const timer = ref(null)
    
    // 组件即将挂载前
    onBeforeMount(() => {
      console.log('Before mount')
    })
    
    // 组件挂载后
    onMounted(() => {
      console.log('Mounted')
      // 初始化数据
      data.value = 'Initial Data'
      
      // 设置定时器
      timer.value = setInterval(() => {
        console.log('Timer tick')
      }, 1000)
    })
    
    // 组件即将更新前
    onBeforeUpdate(() => {
      console.log('Before update')
    })
    
    // 组件更新后
    onUpdated(() => {
      console.log('Updated')
    })
    
    // 组件即将卸载前
    onBeforeUnmount(() => {
      console.log('Before unmount')
      // 清理定时器
      if (timer.value) {
        clearInterval(timer.value)
      }
    })
    
    // 组件卸载后
    onUnmounted(() => {
      console.log('Unmounted')
    })
    
    const updateData = () => {
      data.value = 'Updated Data'
    }
    
    return {
      data,
      updateData
    }
  }
}

复杂组件状态管理实践

用户信息管理组件

// UserInfo.vue
<template>
  <div class="user-info">
    <h2>{{ user.name }}</h2>
    <p>Age: {{ user.age }}</p>
    <p>Email: {{ user.email }}</p>
    <button @click="updateUser">Update User</button>
    <button @click="fetchUserData">Fetch Data</button>
  </div>
</template>

<script>
import { ref, reactive, computed, watch } from 'vue'
import { useStore } from 'vuex' // 或者使用 Pinia

export default {
  name: 'UserInfo',
  setup() {
    // 响应式数据
    const user = reactive({
      name: '',
      age: 0,
      email: ''
    })
    
    const loading = ref(false)
    const error = ref(null)
    
    // 计算属性
    const isAdult = computed(() => {
      return user.age >= 18
    })
    
    const userInfo = computed(() => {
      return {
        ...user,
        isAdult: isAdult.value
      }
    })
    
    // 监听器
    watch(user, (newUser, oldUser) => {
      console.log('User updated:', newUser)
    }, { deep: true })
    
    // 方法
    const fetchUserData = async () => {
      loading.value = true
      error.value = null
      
      try {
        // 模拟 API 调用
        const response = await fetch('/api/user')
        const userData = await response.json()
        
        Object.assign(user, userData)
      } catch (err) {
        error.value = err.message
        console.error('Failed to fetch user data:', err)
      } finally {
        loading.value = false
      }
    }
    
    const updateUser = () => {
      // 模拟更新用户信息
      user.age++
      user.name = `Updated User ${user.age}`
    }
    
    // 初始化数据
    fetchUserData()
    
    return {
      user,
      loading,
      error,
      isAdult,
      userInfo,
      fetchUserData,
      updateUser
    }
  }
}
</script>

表单验证组件

// FormValidator.vue
<template>
  <form @submit="handleSubmit">
    <div class="form-group">
      <label for="email">Email:</label>
      <input 
        id="email" 
        v-model="formData.email" 
        type="email"
        :class="{ 'error': errors.email }"
      />
      <span v-if="errors.email" class="error-message">{{ errors.email }}</span>
    </div>
    
    <div class="form-group">
      <label for="password">Password:</label>
      <input 
        id="password" 
        v-model="formData.password" 
        type="password"
        :class="{ 'error': errors.password }"
      />
      <span v-if="errors.password" class="error-message">{{ errors.password }}</span>
    </div>
    
    <button type="submit" :disabled="isSubmitting || !isValid">Submit</button>
  </form>
</template>

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

export default {
  name: 'FormValidator',
  setup() {
    // 表单数据
    const formData = reactive({
      email: '',
      password: ''
    })
    
    // 错误信息
    const errors = reactive({})
    
    // 提交状态
    const isSubmitting = ref(false)
    
    // 验证规则
    const validationRules = {
      email: [
        value => !!value || 'Email is required',
        value => /\S+@\S+\.\S+/.test(value) || 'Email address is invalid'
      ],
      password: [
        value => !!value || 'Password is required',
        value => value.length >= 6 || 'Password must be at least 6 characters'
      ]
    }
    
    // 计算属性
    const isValid = computed(() => {
      return Object.keys(errors).length === 0
    })
    
    // 验证表单
    const validateField = (field) => {
      const value = formData[field]
      const rules = validationRules[field]
      
      if (!rules) return
      
      for (let rule of rules) {
        const result = rule(value)
        if (result !== true) {
          errors[field] = result
          return
        }
      }
      
      delete errors[field]
    }
    
    // 监听表单变化
    watch(formData, (newData) => {
      Object.keys(newData).forEach(field => {
        validateField(field)
      })
    }, { deep: true })
    
    // 提交表单
    const handleSubmit = async (event) => {
      event.preventDefault()
      
      if (!isValid.value) return
      
      isSubmitting.value = true
      
      try {
        // 模拟提交
        await new Promise(resolve => setTimeout(resolve, 1000))
        console.log('Form submitted:', formData)
        alert('Form submitted successfully!')
      } catch (error) {
        console.error('Submit error:', error)
      } finally {
        isSubmitting.value = false
      }
    }
    
    return {
      formData,
      errors,
      isValid,
      isSubmitting,
      handleSubmit
    }
  }
}
</script>

<style scoped>
.form-group {
  margin-bottom: 1rem;
}

.error {
  border-color: #ff4757;
}

.error-message {
  color: #ff4757;
  font-size: 0.8rem;
}
</style>

组合式函数(Composables)最佳实践

创建可复用的组合式函数

// 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 doubleCount = computed(() => count.value * 2)
  
  return {
    count,
    increment,
    decrement,
    reset,
    doubleCount
  }
}

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

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

// 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
}

使用组合式函数的组件示例

<!-- Counter.vue -->
<template>
  <div class="counter">
    <h2>Counter: {{ count }}</h2>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="reset">Reset</button>
  </div>
</template>

<script>
import { useCounter } from '@/composables/useCounter'

export default {
  setup() {
    const { count, increment, decrement, reset, doubleCount } = useCounter(0)
    
    return {
      count,
      increment,
      decrement,
      reset,
      doubleCount
    }
  }
}
</script>

<!-- ApiComponent.vue -->
<template>
  <div class="api-component">
    <button @click="fetchData">Fetch Data</button>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else-if="data">
      <pre>{{ JSON.stringify(data, null, 2) }}</pre>
    </div>
  </div>
</template>

<script>
import { useApi } from '@/composables/useApi'

export default {
  setup() {
    const { loading, error, data, fetchData } = useApi()
    
    return {
      loading,
      error,
      data,
      fetchData
    }
  }
}
</script>

高级特性与最佳实践

异步数据加载优化

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

export default {
  setup() {
    const data = ref(null)
    const loading = ref(false)
    const error = ref(null)
    const abortController = ref(null)
    
    // 防抖函数
    const debounce = (func, wait) => {
      let timeout
      return (...args) => {
        clearTimeout(timeout)
        timeout = setTimeout(() => func.apply(this, args), wait)
      }
    }
    
    // 带防抖的数据加载
    const debouncedFetchData = debounce(async (url) => {
      if (abortController.value) {
        abortController.value.abort()
      }
      
      abortController.value = new AbortController()
      
      try {
        loading.value = true
        error.value = null
        
        const response = await fetch(url, {
          signal: abortController.value.signal
        })
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`)
        }
        
        const result = await response.json()
        data.value = result
      } catch (err) {
        if (err.name !== 'AbortError') {
          error.value = err.message
        }
      } finally {
        loading.value = false
      }
    }, 300)
    
    // 取消请求
    const cancelRequest = () => {
      if (abortController.value) {
        abortController.value.abort()
      }
    }
    
    // 组件卸载时清理
    onUnmounted(() => {
      cancelRequest()
    })
    
    return {
      data,
      loading,
      error,
      debouncedFetchData,
      cancelRequest
    }
  }
}

性能优化技巧

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

export default {
  setup() {
    // 使用 shallowRef 进行浅层响应式
    const shallowData = shallowRef({
      user: { name: 'John' },
      items: [1, 2, 3]
    })
    
    // 使用 markRaw 避免不必要的响应式转换
    const rawData = {
      apiClient: {
        get: () => {},
        post: () => {}
      }
    }
    
    const processedData = markRaw(rawData)
    
    // 计算属性缓存优化
    const expensiveComputation = computed(() => {
      // 复杂计算逻辑
      return Array.from({ length: 1000 }, (_, i) => i * 2)
    })
    
    // 监听器优化
    const watchOptions = {
      flush: 'post', // 在组件更新后执行
      deep: true,
      immediate: false
    }
    
    const handleDataChange = (newVal, oldVal) => {
      console.log('Data changed:', newVal)
    }
    
    return {
      shallowData,
      processedData,
      expensiveComputation,
      handleDataChange
    }
  }
}

错误处理和边界情况

import { ref, reactive, onErrorCaptured, onUnmounted } from 'vue'

export default {
  setup() {
    const data = ref(null)
    const loading = ref(false)
    const error = ref(null)
    
    // 全局错误捕获
    onErrorCaptured((err, instance, info) => {
      console.error('Error captured:', err, info)
      return false // 阻止错误继续传播
    })
    
    // 优雅降级处理
    const safeFetch = async (url) => {
      try {
        loading.value = true
        error.value = null
        
        const response = await fetch(url)
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`)
        }
        
        const result = await response.json()
        data.value = result
        
        return result
      } catch (err) {
        console.error('Fetch error:', err)
        error.value = `Failed to load data: ${err.message}`
        
        // 提供默认数据
        data.value = getDefaultData()
        return null
      } finally {
        loading.value = false
      }
    }
    
    const getDefaultData = () => ({
      message: 'Default data loaded',
      timestamp: Date.now()
    })
    
    const retryFetch = () => {
      // 重试逻辑
      if (error.value) {
        safeFetch('/api/data')
      }
    }
    
    return {
      data,
      loading,
      error,
      safeFetch,
      retryFetch
    }
  }
}

总结

Vue 3 的 Composition API 为前端开发者提供了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们可以看到:

  1. 响应式数据处理refreactive 提供了丰富的响应式能力
  2. 生命周期管理onMountedonUpdated 等钩子函数帮助我们精确控制组件生命周期
  3. 组合式函数:通过自定义组合式函数实现逻辑复用,提高代码可维护性
  4. 性能优化:合理使用 computedwatch 选项和 shallowRef 等工具优化性能
  5. 错误处理:完善的错误捕获和降级机制确保应用稳定性

在实际项目中,建议根据具体需求选择合适的 API 风格。对于简单组件,可以继续使用 Options API;对于复杂组件,Composition API 能够提供更好的组织方式和复用能力。同时,合理使用组合式函数可以让代码更加模块化,提高开发效率。

通过掌握这些核心技术,开发者能够构建出更加健壮、可维护的 Vue 3 应用程序,充分利用现代前端开发的最佳实践。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000