Vue 3 Composition API实战:从基础语法到复杂组件状态管理的完整教程

Kevin272
Kevin272 2026-03-01T02:07:05+08:00
0 0 0

前言

Vue 3的发布带来了全新的开发体验,其中最引人注目的特性之一就是Composition API。相比于Vue 2的Options API,Composition API提供了一种更加灵活、强大的组件状态管理方式。本文将深入探讨Composition API的核心概念、实际应用场景以及最佳实践,帮助开发者从基础语法逐步掌握这一现代Vue开发模式。

什么是Composition API

核心概念

Composition API是Vue 3中引入的一种新的组件状态管理方式,它将组件的逻辑组织方式从传统的Options API(选项式API)转变为基于函数的组合式API。这种设计模式使得开发者可以更灵活地组织和复用组件逻辑。

在传统的Options API中,组件的逻辑被分散在不同的选项中(data、methods、computed、watch等),而Composition API允许我们将相关的逻辑组织在一起,形成更清晰的代码结构。

与Options API的区别

// Vue 2 Options API
export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

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

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

响应式数据管理

基础响应式API

Composition API提供了多种响应式数据管理的API,其中最核心的是refreactive

Ref API

ref用于创建响应式的数据引用,它可以处理基本数据类型和对象类型:

import { ref } from 'vue'

// 基本数据类型
const count = ref(0)
const message = ref('Hello Vue')

// 对象类型
const user = ref({
  name: 'John',
  age: 30
})

// 访问和修改
console.log(count.value) // 0
count.value = 10
console.log(count.value) // 10

// 对象属性访问
console.log(user.value.name) // John
user.value.name = 'Jane'

Reactive API

reactive用于创建响应式对象,它会将对象的所有属性都转换为响应式:

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'John',
    age: 30
  }
})

// 修改属性
state.count = 5
state.user.name = 'Jane'

// 直接赋值对象会丢失响应性
// state = { count: 10 } // 这样会丢失响应性

深度响应式与浅响应式

import { reactive, shallowReactive, toRaw } from 'vue'

// 深度响应式
const deepState = reactive({
  nested: {
    deep: {
      value: 1
    }
  }
})
// 修改深层属性会触发响应
deepState.nested.deep.value = 2

// 浅响应式
const shallowState = shallowReactive({
  nested: {
    deep: {
      value: 1
    }
  }
})
// 只有顶层属性是响应式的
shallowState.nested = { deep: { value: 2 } } // 会触发响应
shallowState.nested.deep.value = 2 // 不会触发响应

响应式数组和Map

import { ref, reactive } from 'vue'

// 数组响应式
const list = ref([1, 2, 3])
list.value.push(4) // 会触发响应
list.value[0] = 10 // 会触发响应

// 对象响应式
const map = reactive(new Map())
map.set('key', 'value') // 会触发响应

组合函数(Composables)的使用

创建可复用的逻辑

组合函数是Composition API的核心特性之一,它允许我们将可复用的逻辑封装成函数:

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

// 使用组合函数
import { useCounter } from '@/composables/useCounter'

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

复杂组合函数示例

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

export function useFetch(url) {
  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)
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  // 自动获取数据
  fetchData()
  
  // 监听url变化
  watch(url, fetchData)
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

// 使用示例
export default {
  setup() {
    const { data, loading, error, refetch } = useFetch('/api/users')
    
    return {
      data,
      loading,
      error,
      refetch
    }
  }
}

组件通信模式

父子组件通信

Props传递

// 父组件
import { ref } from 'vue'

export default {
  setup() {
    const message = ref('Hello from parent')
    const count = ref(0)
    
    return {
      message,
      count
    }
  }
}

// 子组件
export default {
  props: {
    message: String,
    count: Number
  },
  setup(props) {
    // props是响应式的
    console.log(props.message) // 'Hello from parent'
    
    return {}
  }
}

emit事件

// 子组件
export default {
  emits: ['update-count', 'submit'],
  setup(props, { emit }) {
    const handleIncrement = () => {
      emit('update-count', props.count + 1)
    }
    
    const handleSubmit = (data) => {
      emit('submit', data)
    }
    
    return {
      handleIncrement,
      handleSubmit
    }
  }
}

// 父组件
export default {
  setup() {
    const count = ref(0)
    
    const handleUpdateCount = (newCount) => {
      count.value = newCount
    }
    
    return {
      count,
      handleUpdateCount
    }
  }
}

兄弟组件通信

// composables/useEventBus.js
import { reactive } from 'vue'

export function useEventBus() {
  const events = reactive({})
  
  const on = (event, callback) => {
    if (!events[event]) {
      events[event] = []
    }
    events[event].push(callback)
  }
  
  const emit = (event, data) => {
    if (events[event]) {
      events[event].forEach(callback => callback(data))
    }
  }
  
  const off = (event, callback) => {
    if (events[event]) {
      events[event] = events[event].filter(cb => cb !== callback)
    }
  }
  
  return {
    on,
    emit,
    off
  }
}

// 使用示例
// ComponentA.vue
import { useEventBus } from '@/composables/useEventBus'

export default {
  setup() {
    const { emit } = useEventBus()
    
    const sendMessage = () => {
      emit('message', 'Hello from Component A')
    }
    
    return {
      sendMessage
    }
  }
}

// ComponentB.vue
import { useEventBus } from '@/composables/useEventBus'

export default {
  setup() {
    const { on } = useEventBus()
    const message = ref('')
    
    on('message', (data) => {
      message.value = data
    })
    
    return {
      message
    }
  }
}

高级响应式特性

计算属性和监听器

import { ref, computed, watch, watchEffect } 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}`
    })
    
    const isAdult = computed(() => {
      return age.value >= 18
    })
    
    // 监听器
    const watchCount = ref(0)
    
    // 基本监听
    watch(watchCount, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    })
    
    // 监听多个源
    watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
      console.log(`Name changed from ${oldFirst} ${oldLast} to ${newFirst} ${newLast}`)
    })
    
    // 深度监听
    const nestedData = ref({
      user: {
        profile: {
          name: 'John'
        }
      }
    })
    
    watch(nestedData, (newVal, oldVal) => {
      console.log('Nested data changed')
    }, { deep: true })
    
    // watchEffect
    const effectCount = ref(0)
    
    watchEffect(() => {
      console.log(`Effect: ${effectCount.value}`)
      // 这个effect会自动追踪所有响应式依赖
    })
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      isAdult,
      watchCount,
      effectCount
    }
  }
}

异步操作和生命周期

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

export default {
  setup() {
    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('/api/data')
        data.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 生命周期钩子
    onMounted(() => {
      console.log('Component mounted')
      fetchData()
    })
    
    onUnmounted(() => {
      console.log('Component unmounted')
    })
    
    onUpdated(() => {
      console.log('Component updated')
    })
    
    // 清理定时器
    const timer = ref(null)
    
    onMounted(() => {
      timer.value = setInterval(() => {
        console.log('Timer tick')
      }, 1000)
    })
    
    onUnmounted(() => {
      if (timer.value) {
        clearInterval(timer.value)
      }
    })
    
    return {
      data,
      loading,
      error,
      fetchData
    }
  }
}

复杂组件状态管理

状态管理模式

// stores/useGlobalStore.js
import { reactive, readonly } from 'vue'

export function useGlobalStore() {
  const state = reactive({
    user: null,
    theme: 'light',
    notifications: []
  })
  
  const setUser = (user) => {
    state.user = user
  }
  
  const setTheme = (theme) => {
    state.theme = theme
  }
  
  const addNotification = (notification) => {
    state.notifications.push({
      id: Date.now(),
      ...notification,
      timestamp: new Date()
    })
  }
  
  const removeNotification = (id) => {
    state.notifications = state.notifications.filter(n => n.id !== id)
  }
  
  const clearNotifications = () => {
    state.notifications = []
  }
  
  return {
    state: readonly(state),
    setUser,
    setTheme,
    addNotification,
    removeNotification,
    clearNotifications
  }
}

// 在组件中使用
export default {
  setup() {
    const { state, setUser, setTheme } = useGlobalStore()
    
    const handleLogin = async (credentials) => {
      try {
        const user = await login(credentials)
        setUser(user)
      } catch (error) {
        console.error('Login failed:', error)
      }
    }
    
    return {
      state,
      handleLogin,
      setTheme
    }
  }
}

表单处理

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

export function useForm(initialData = {}) {
  const formData = reactive({ ...initialData })
  const errors = ref({})
  const isSubmitting = ref(false)
  
  const validate = (rules) => {
    const newErrors = {}
    
    Object.keys(rules).forEach(field => {
      const rule = rules[field]
      const value = formData[field]
      
      if (rule.required && !value) {
        newErrors[field] = `${field} is required`
      }
      
      if (rule.minLength && value.length < rule.minLength) {
        newErrors[field] = `${field} must be at least ${rule.minLength} characters`
      }
      
      if (rule.email && !/\S+@\S+\.\S+/.test(value)) {
        newErrors[field] = 'Invalid email format'
      }
    })
    
    errors.value = newErrors
    return Object.keys(newErrors).length === 0
  }
  
  const handleSubmit = async (submitFn) => {
    if (!validate()) return
    
    isSubmitting.value = true
    
    try {
      const result = await submitFn(formData)
      return result
    } catch (error) {
      console.error('Form submission error:', error)
      throw error
    } finally {
      isSubmitting.value = false
    }
  }
  
  const reset = () => {
    Object.keys(formData).forEach(key => {
      formData[key] = initialData[key] || ''
    })
    errors.value = {}
  }
  
  const setField = (field, value) => {
    formData[field] = value
  }
  
  return {
    formData,
    errors,
    isSubmitting,
    validate,
    handleSubmit,
    reset,
    setField
  }
}

// 使用示例
export default {
  setup() {
    const { 
      formData, 
      errors, 
      isSubmitting, 
      validate, 
      handleSubmit,
      reset 
    } = useForm({
      name: '',
      email: '',
      message: ''
    })
    
    const rules = {
      name: { required: true, minLength: 2 },
      email: { required: true, email: true },
      message: { required: true, minLength: 10 }
    }
    
    const handleFormSubmit = async (data) => {
      // 提交表单逻辑
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      })
      
      return response.json()
    }
    
    const onSubmit = async () => {
      await handleSubmit(handleFormSubmit)
    }
    
    return {
      formData,
      errors,
      isSubmitting,
      onSubmit,
      reset
    }
  }
}

最佳实践和性能优化

组合函数设计原则

// 好的设计模式
// composables/useApi.js
import { ref, watch } from 'vue'

export function useApi(url, options = {}) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const retryCount = ref(0)
  
  const fetch = async () => {
    if (loading.value) return
    
    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()
      retryCount.value = 0
    } catch (err) {
      error.value = err
      retryCount.value++
    } finally {
      loading.value = false
    }
  }
  
  const retry = () => {
    fetch()
  }
  
  // 自动刷新
  if (options.autoFetch !== false) {
    fetch()
  }
  
  // 监听url变化
  watch(url, fetch)
  
  return {
    data,
    loading,
    error,
    retry,
    retryCount,
    fetch
  }
}

性能优化技巧

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

export default {
  setup() {
    const list = ref([])
    const searchTerm = ref('')
    const debouncedSearch = ref('')
    
    // 防抖搜索
    const debounce = (func, delay) => {
      let timeoutId
      return (...args) => {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => func.apply(this, args), delay)
      }
    }
    
    const debouncedSearchHandler = debounce((value) => {
      debouncedSearch.value = value
    }, 300)
    
    // 计算属性优化
    const filteredList = computed(() => {
      if (!debouncedSearch.value) return list.value
      
      return list.value.filter(item => 
        item.name.toLowerCase().includes(debouncedSearch.value.toLowerCase())
      )
    })
    
    // 监听优化
    const handleSearch = (value) => {
      searchTerm.value = value
      debouncedSearchHandler(value)
    }
    
    // 资源清理
    let timer = null
    
    onMounted(() => {
      timer = setInterval(() => {
        // 定期执行的逻辑
      }, 1000)
    })
    
    onUnmounted(() => {
      if (timer) {
        clearInterval(timer)
      }
    })
    
    return {
      list,
      searchTerm,
      filteredList,
      handleSearch
    }
  }
}

总结

Vue 3的Composition API为现代前端开发提供了强大的工具集。通过本文的详细介绍,我们看到了从基础语法到复杂应用的完整学习路径。Composition API的优势在于:

  1. 更好的逻辑组织:将相关的逻辑组合在一起,提高代码可读性和维护性
  2. 更强的复用能力:通过组合函数实现逻辑复用
  3. 更灵活的开发模式:相比Options API更加灵活
  4. 更好的TypeScript支持:提供更好的类型推断能力

在实际开发中,建议根据项目需求选择合适的API模式,对于简单组件可以使用Options API,对于复杂组件则推荐使用Composition API。同时,要善于利用组合函数来封装可复用的逻辑,这样可以让代码更加模块化和易于维护。

随着Vue生态的不断发展,Composition API将成为现代Vue开发的标准模式,掌握它对于前端开发者来说是必不可少的技能。通过本文的学习,相信读者已经对Vue 3 Composition API有了深入的理解,并能够在实际项目中灵活运用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000