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

WiseFace
WiseFace 2026-03-01T04:05:03+08:00
0 0 0

前言

Vue 3的发布带来了革命性的变化,其中最引人注目的就是Composition API的引入。相比于Vue 2的选项式API,Composition API为开发者提供了更灵活、更强大的组件状态管理方式。本文将深入探讨Composition API的核心概念和使用方法,帮助开发者构建更灵活、可维护的Vue应用。

什么是Composition API

Composition API是Vue 3中引入的一种新的组件状态管理方式,它将组件的逻辑按照功能进行组织,而不是按照选项(如data、methods、computed等)进行分组。这种设计模式使得代码更加模块化,更容易复用和维护。

Composition API的核心优势

  1. 更好的逻辑复用:通过组合式函数(Composable Functions)实现逻辑复用
  2. 更灵活的代码组织:按照功能逻辑组织代码,而非选项类型
  3. 更好的类型支持:与TypeScript集成更佳
  4. 更清晰的代码结构:避免了选项式API中的this指向问题

基础概念与核心API

reactive与ref

在Composition API中,响应式数据的创建主要通过reactiveref两个核心API实现。

import { reactive, ref } from 'vue'

// ref用于创建响应式的基本数据类型
const count = ref(0)
const name = ref('Vue')

// reactive用于创建响应式对象
const state = reactive({
  count: 0,
  name: 'Vue',
  user: {
    age: 20,
    email: 'vue@example.com'
  }
})

// 使用时需要通过.value访问ref的值
console.log(count.value) // 0
count.value = 10
console.log(count.value) // 10

// reactive对象可以直接访问属性
console.log(state.count) // 0
state.count = 10
console.log(state.count) // 10

computed

computed用于创建计算属性,它会自动追踪依赖并缓存结果:

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// 基本计算属性
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})

// 带有getter和setter的计算属性
const reversedFullName = computed({
  get: () => {
    return `${lastName.value} ${firstName.value}`
  },
  set: (value) => {
    const names = value.split(' ')
    firstName.value = names[0]
    lastName.value = names[1]
  }
})

console.log(fullName.value) // John Doe
reversedFullName.value = 'Smith John'
console.log(firstName.value) // John
console.log(lastName.value) // Smith

watch与watchEffect

watchwatchEffect用于监听响应式数据的变化:

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

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

// 基本watch用法
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}`)
  console.log(`当前name值: ${name.value}`)
})

// 停止监听
const stop = watch(count, (newVal) => {
  console.log(`监听到变化: ${newVal}`)
})
// 调用stop()停止监听
// stop()

生命周期钩子

Composition API提供了与Vue 2选项式API对应的生命周期钩子:

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

export default {
  setup() {
    // 组件挂载时调用
    onMounted(() => {
      console.log('组件已挂载')
      // 可以在这里执行DOM操作
    })

    // 组件更新时调用
    onUpdated(() => {
      console.log('组件已更新')
    })

    // 组件卸载前调用
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
      // 清理定时器、事件监听器等
    })

    return {
      // 返回的数据和方法
    }
  }
}

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

在组件中使用组合式函数

<template>
  <div>
    <p>计数: {{ count }}</p>
    <p>双倍计数: {{ doubleCount }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <button @click="reset">重置</button>
  </div>
</template>

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

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

复杂的组合式函数示例

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

export function useApi(url, options = {}) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const response = reactive({
    status: null,
    headers: null
  })
  
  const fetchData = async () => {
    loading.value = true
    error.value = null
    
    try {
      const res = await fetch(url, options)
      response.status = res.status
      response.headers = Object.fromEntries(res.headers.entries())
      
      if (!res.ok) {
        throw new Error(`HTTP error! status: ${res.status}`)
      }
      
      data.value = await res.json()
    } catch (err) {
      error.value = err.message
      console.error('API请求失败:', err)
    } finally {
      loading.value = false
    }
  }
  
  // 自动执行请求
  if (options.autoFetch !== false) {
    fetchData()
  }
  
  // 监听url变化,自动重新请求
  watch(url, fetchData)
  
  return {
    data,
    loading,
    error,
    response,
    refetch: fetchData
  }
}

复杂组件状态管理

多层级组件的状态管理

在复杂的组件树中,状态管理变得尤为重要。我们可以通过组合式函数来管理跨组件的状态:

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

// 全局用户状态
const user = reactive({
  id: null,
  name: '',
  email: '',
  isLoggedIn: false
})

const setUser = (userData) => {
  Object.assign(user, userData)
}

const clearUser = () => {
  user.id = null
  user.name = ''
  user.email = ''
  user.isLoggedIn = false
}

export function useUser() {
  return {
    user: readonly(user),
    setUser,
    clearUser
  }
}

表单状态管理

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

export function useForm(initialData = {}) {
  const formData = reactive({ ...initialData })
  const errors = reactive({})
  const isSubmitting = ref(false)
  
  const isValid = computed(() => {
    return Object.keys(errors).length === 0
  })
  
  const validateField = (field, value) => {
    // 简单的验证规则
    if (field === 'email' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      errors[field] = '请输入有效的邮箱地址'
    } else if (field === 'password' && value && value.length < 6) {
      errors[field] = '密码长度至少6位'
    } else {
      delete errors[field]
    }
  }
  
  const validateAll = () => {
    Object.keys(formData).forEach(field => {
      validateField(field, formData[field])
    })
  }
  
  const setField = (field, value) => {
    formData[field] = value
    validateField(field, value)
  }
  
  const submit = async (submitHandler) => {
    if (!isValid.value) {
      validateAll()
      return false
    }
    
    isSubmitting.value = true
    try {
      const result = await submitHandler(formData)
      return result
    } catch (error) {
      console.error('提交失败:', error)
      return false
    } finally {
      isSubmitting.value = false
    }
  }
  
  return {
    formData,
    errors,
    isValid,
    isSubmitting,
    setField,
    submit
  }
}

实际应用案例

实时搜索功能

<template>
  <div>
    <input 
      v-model="searchQuery" 
      placeholder="搜索..."
      @input="debouncedSearch"
    />
    <div v-if="loading">搜索中...</div>
    <div v-else-if="error">{{ error }}</div>
    <ul v-else>
      <li v-for="item in searchResults" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

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

export default {
  setup() {
    const searchQuery = ref('')
    const searchResults = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    const debouncedSearch = debounce(async () => {
      if (!searchQuery.value.trim()) {
        searchResults.value = []
        return
      }
      
      loading.value = true
      error.value = null
      
      try {
        const response = await fetch(`/api/search?q=${searchQuery.value}`)
        if (!response.ok) {
          throw new Error('搜索失败')
        }
        searchResults.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }, 300)
    
    // 清除搜索
    watch(searchQuery, () => {
      if (!searchQuery.value.trim()) {
        searchResults.value = []
      }
    })
    
    return {
      searchQuery,
      searchResults,
      loading,
      error,
      debouncedSearch
    }
  }
}

// 防抖函数
function debounce(func, wait) {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}
</script>

模态框组件

<template>
  <div class="modal-overlay" v-if="isVisible" @click="close">
    <div class="modal-content" @click.stop>
      <div class="modal-header">
        <h3>{{ title }}</h3>
        <button @click="close">&times;</button>
      </div>
      <div class="modal-body">
        <slot></slot>
      </div>
      <div class="modal-footer">
        <button @click="close">取消</button>
        <button @click="confirm">确认</button>
      </div>
    </div>
  </div>
</template>

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

export default {
  props: {
    title: String,
    visible: Boolean
  },
  emits: ['confirm', 'close'],
  setup(props, { emit }) {
    const isVisible = ref(false)
    
    watch(() => props.visible, (newVal) => {
      isVisible.value = newVal
    })
    
    const close = () => {
      isVisible.value = false
      emit('close')
    }
    
    const confirm = () => {
      emit('confirm')
      close()
    }
    
    return {
      isVisible,
      close,
      confirm
    }
  }
}
</script>

最佳实践与性能优化

合理使用响应式API

// ❌ 不好的做法
export default {
  setup() {
    const state = reactive({
      user: null,
      posts: [],
      loading: false,
      error: null
    })
    
    // 每次都创建新的函数
    const fetchUser = () => {
      // ...
    }
    
    const fetchPosts = () => {
      // ...
    }
    
    return {
      ...state,
      fetchUser,
      fetchPosts
    }
  }
}

// ✅ 好的做法
export default {
  setup() {
    const user = ref(null)
    const posts = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    // 使用箭头函数保持this指向
    const fetchUser = async () => {
      // ...
    }
    
    const fetchPosts = async () => {
      // ...
    }
    
    return {
      user,
      posts,
      loading,
      error,
      fetchUser,
      fetchPosts
    }
  }
}

组合式函数的命名规范

// ✅ 好的命名规范
// useUser.js - 用户相关逻辑
// useApi.js - API请求逻辑
// useForm.js - 表单处理逻辑
// useStorage.js - 本地存储逻辑

// 组合式函数应该以use开头,遵循命名约定
export function useCounter(initialValue = 0) {
  // ...
}

export function useUser() {
  // ...
}

export function useTheme() {
  // ...
}

避免不必要的响应式转换

// ❌ 不好的做法
const state = reactive({
  // 大量不需要响应式的静态数据
  staticData: {
    // ...
  }
})

// ✅ 好的做法
const staticData = {
  // 静态数据不需要响应式
}

const state = reactive({
  // 只包含需要响应式的动态数据
  count: 0,
  user: null
})

与Vue 2选项式API的对比

相同点

// Vue 2 选项式API
export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    fullName() {
      return `${this.name} 3`
    }
  },
  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 fullName = computed(() => {
      return `${name.value} 3`
    })
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      name,
      fullName,
      increment
    }
  }
}

不同点

  1. 代码组织方式:Composition API按功能组织,选项式API按类型组织
  2. 逻辑复用:Composition API通过组合式函数实现复用,选项式API通过mixins实现
  3. this指向:Composition API中不再需要处理this指向问题
  4. 类型支持:Composition API与TypeScript集成更佳

总结

Vue 3的Composition API为前端开发者提供了一种更灵活、更强大的组件状态管理方式。通过合理使用refreactivecomputedwatch等核心API,以及创建可复用的组合式函数,我们可以构建出更加模块化、可维护的Vue应用。

在实际开发中,建议:

  1. 优先使用Composition API来组织组件逻辑
  2. 合理封装组合式函数来实现逻辑复用
  3. 注意性能优化,避免不必要的响应式转换
  4. 遵循命名规范,保持代码的一致性

随着Vue生态的不断发展,Composition API将成为构建现代Vue应用的标准方式。掌握这些核心概念和最佳实践,将帮助开发者更好地应对复杂的应用开发需求。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000