Vue 3 Composition API最佳实践:从基础到高级的完整开发指南

Helen635
Helen635 2026-02-02T10:10:16+08:00
0 0 1

引言

Vue 3 的发布带来了全新的 Composition API,这是一次革命性的变化,它重新定义了 Vue 组件的编写方式。相比 Vue 2 中的 Options API,Composition API 提供了更灵活、更强大的组件逻辑组织方式,特别是在处理复杂组件逻辑时表现尤为突出。

本文将深入探讨 Vue 3 Composition API 的核心概念和实际应用,从基础概念到高级技巧,帮助开发者掌握这一现代前端开发的重要技能。我们将涵盖组件逻辑复用、响应式数据处理、生命周期管理等关键技能,并提供实用的最佳实践建议。

什么是 Composition API

核心概念

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们使用函数来组织和复用组件逻辑,而不是传统的选项(options)形式。通过 Composition API,我们可以将相关的逻辑代码组织在一起,提高代码的可维护性和可读性。

与 Options API 的对比

在 Vue 2 中,组件逻辑主要通过 options 来组织:

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

而在 Vue 3 中,我们可以使用 Composition API:

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

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

响应式数据处理

ref 和 reactive 的基本使用

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

import { ref, reactive } from 'vue'

// 使用 ref 创建响应式数据
const count = ref(0)
const name = ref('Vue')

// 使用 reactive 创建响应式对象
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
})

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

console.log(user.firstName) // John
user.firstName = 'Jane'

深层响应式数据处理

对于深层嵌套的对象,我们需要使用 reactive 来确保所有层级都是响应式的:

import { reactive } from 'vue'

const state = reactive({
  user: {
    profile: {
      name: 'John',
      settings: {
        theme: 'dark'
      }
    }
  }
})

// 修改深层属性
state.user.profile.name = 'Jane'
state.user.profile.settings.theme = 'light'

ref vs reactive 的选择

// 对于基本类型数据,推荐使用 ref
const count = ref(0)
const message = ref('Hello')

// 对于对象或数组,推荐使用 reactive
const userInfo = reactive({
  name: 'John',
  age: 30,
  hobbies: ['reading', 'coding']
})

const userList = reactive([])

响应式数据的解构和展开

import { ref, reactive } from 'vue'

// 注意:直接解构会失去响应性
const state = reactive({
  count: 0,
  name: 'Vue'
})

// ❌ 错误方式 - 失去响应性
const { count, name } = state

// ✅ 正确方式 - 保持响应性
const countRef = ref(state.count)
const nameRef = ref(state.name)

// 或者使用 toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)

生命周期钩子

基本生命周期管理

Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数形式提供:

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

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

在不同生命周期阶段的使用场景

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

export default {
  setup() {
    const timer = ref(null)
    const count = ref(0)
    
    // 设置定时器 - 组件挂载后
    onMounted(() => {
      timer.value = setInterval(() => {
        count.value++
      }, 1000)
    })
    
    // 清理定时器 - 组件卸载前
    onUnmounted(() => {
      if (timer.value) {
        clearInterval(timer.value)
      }
    })
    
    return {
      count
    }
  }
}

组件逻辑复用

Composables 的概念和实践

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

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

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

复杂的 Composables 实例

// 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)
      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
    }
  }
  
  // 立即执行一次
  fetchData()
  
  // 监听 URL 变化,重新获取数据
  watch(url, fetchData)
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

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

export default {
  setup() {
    const url = ref('https://api.example.com/users')
    const { data, loading, error, refetch } = useFetch(url)
    
    return {
      data,
      loading,
      error,
      refetch
    }
  }
}

带状态管理的 Composables

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

export function useLocalStorage(key, defaultValue) {
  const value = ref(defaultValue)
  
  // 从 localStorage 初始化值
  const storedValue = localStorage.getItem(key)
  if (storedValue) {
    try {
      value.value = JSON.parse(storedValue)
    } catch (e) {
      console.error('Failed to parse localStorage item:', e)
    }
  }
  
  // 监听值变化并同步到 localStorage
  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('userPreferences', {})
    
    return {
      theme,
      preferences
    }
  }
}

计算属性和监听器

computed 的高级用法

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('')
    const lastName = ref('')
    const age = ref(0)
    
    // 基本计算属性
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // 带 getter 和 setter 的计算属性
    const displayName = computed({
      get: () => {
        if (age.value >= 18) {
          return `Mr. ${fullName.value}`
        }
        return fullName.value
      },
      set: (value) => {
        const names = value.split(' ')
        firstName.value = names[0]
        lastName.value = names[1] || ''
      }
    })
    
    // 计算属性的依赖追踪
    const isAdult = computed(() => age.value >= 18)
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      displayName,
      isAdult
    }
  }
}

watch 的使用技巧

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    const user = ref({ id: 1, name: 'John' })
    
    // 基本监听器
    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) => {
      console.log('count is now:', newVal)
    }, { immediate: true })
    
    // watchEffect - 自动追踪依赖
    const watchEffectExample = watchEffect(() => {
      console.log(`Name is: ${name.value}, Count is: ${count.value}`)
      // 会自动追踪 name 和 count 的变化
    })
    
    return {
      count,
      name,
      user
    }
  }
}

事件处理和方法定义

方法的定义和使用

import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('')
    
    // 定义方法
    const increment = () => {
      count.value++
    }
    
    const decrement = () => {
      count.value--
    }
    
    const reset = () => {
      count.value = 0
    }
    
    const updateMessage = (newMessage) => {
      message.value = newMessage
    }
    
    // 处理表单提交
    const handleSubmit = (event) => {
      event.preventDefault()
      console.log('Form submitted with:', message.value)
      // 处理提交逻辑
    }
    
    return {
      count,
      message,
      increment,
      decrement,
      reset,
      updateMessage,
      handleSubmit
    }
  }
}

高级事件处理模式

import { ref, watch } from 'vue'

export default {
  setup() {
    const input = ref('')
    const debouncedInput = ref('')
    
    // 防抖处理
    const debounce = (func, delay) => {
      let timeoutId
      return (...args) => {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => func.apply(this, args), delay)
      }
    }
    
    const handleInputChange = debounce((value) => {
      debouncedInput.value = value
      console.log('Debounced input:', value)
    }, 300)
    
    // 节流处理
    const throttle = (func, limit) => {
      let inThrottle
      return (...args) => {
        if (!inThrottle) {
          func.apply(this, args)
          inThrottle = true
          setTimeout(() => inThrottle = false, limit)
        }
      }
    }
    
    const handleScroll = throttle(() => {
      console.log('Scroll event handled')
    }, 100)
    
    // 监听输入变化
    watch(input, (newVal) => {
      handleInputChange(newVal)
    })
    
    return {
      input,
      debouncedInput,
      handleScroll
    }
  }
}

组件通信

父子组件通信

<!-- Parent.vue -->
<template>
  <div>
    <h2>Parent Component</h2>
    <Child 
      :message="parentMessage" 
      @child-event="handleChildEvent"
    />
    <p>Received from child: {{ childMessage }}</p>
  </div>
</template>

<script>
import { ref } from 'vue'
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  setup() {
    const parentMessage = ref('Hello from parent')
    const childMessage = ref('')
    
    const handleChildEvent = (message) => {
      childMessage.value = message
      console.log('Received from child:', message)
    }
    
    return {
      parentMessage,
      childMessage,
      handleChildEvent
    }
  }
}
</script>
<!-- Child.vue -->
<template>
  <div>
    <h3>Child Component</h3>
    <p>{{ message }}</p>
    <button @click="sendToParent">Send to Parent</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  props: ['message'],
  emits: ['child-event'],
  setup(props, { emit }) {
    const sendToParent = () => {
      emit('child-event', `Hello from child at ${new Date()}`)
    }
    
    return {
      sendToParent
    }
  }
}
</script>

兄弟组件通信

<!-- ComponentA.vue -->
<template>
  <div>
    <h3>Component A</h3>
    <input v-model="sharedData" placeholder="Type something..." />
  </div>
</template>

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

export default {
  setup() {
    const { sharedData } = useSharedState()
    
    return {
      sharedData
    }
  }
}
</script>
// composables/useSharedState.js
import { ref } from 'vue'

const sharedData = ref('')

export function useSharedState() {
  return {
    sharedData
  }
}

性能优化技巧

避免不必要的重渲染

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

export default {
  setup() {
    const count = ref(0)
    
    // 使用 computed 缓存计算结果
    const expensiveComputation = computed(() => {
      // 模拟耗时计算
      let result = 0
      for (let i = 0; i < 1000000; i++) {
        result += i
      }
      return result * count.value
    })
    
    // 使用 shallowRef 浅层响应式,避免深度遍历
    const shallowData = shallowRef({
      nested: { value: 'test' }
    })
    
    // 使用 markRaw 避免对象被响应式化
    const rawObject = markRaw({
      method() {
        return 'raw method'
      }
    })
    
    return {
      count,
      expensiveComputation,
      shallowData,
      rawObject
    }
  }
}

组件懒加载和优化

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const showComponent = ref(false)
    const componentLoaded = ref(false)
    
    const loadComponent = async () => {
      if (!componentLoaded.value) {
        // 动态导入组件
        const { default: LazyComponent } = await import('./LazyComponent.vue')
        // 组件加载后设置标志
        componentLoaded.value = true
      }
      showComponent.value = true
    }
    
    onMounted(() => {
      // 延迟加载组件以优化性能
      setTimeout(loadComponent, 1000)
    })
    
    return {
      showComponent,
      loadComponent
    }
  }
}

最佳实践和常见陷阱

推荐的代码组织方式

// ✅ 好的做法:逻辑分组
export default {
  setup() {
    // 1. 响应式数据声明
    const count = ref(0)
    const user = reactive({ name: '', email: '' })
    
    // 2. 计算属性
    const isAdult = computed(() => user.age >= 18)
    const displayName = computed(() => `${user.firstName} ${user.lastName}`)
    
    // 3. 方法定义
    const increment = () => count.value++
    const reset = () => {
      count.value = 0
      user.name = ''
    }
    
    // 4. 生命周期钩子
    onMounted(() => {
      console.log('Component mounted')
    })
    
    // 5. 监听器
    watch(count, (newVal) => {
      console.log('Count changed:', newVal)
    })
    
    return {
      count,
      user,
      isAdult,
      displayName,
      increment,
      reset
    }
  }
}

常见陷阱和解决方案

// ❌ 错误:直接返回 ref 值
export default {
  setup() {
    const count = ref(0)
    
    // 这样做会丢失响应性
    return {
      count: count.value // 应该是 count
    }
  }
}

// ✅ 正确做法
export default {
  setup() {
    const count = ref(0)
    
    return {
      count // 直接返回 ref 对象
    }
  }
}

// ❌ 错误:在函数内部创建响应式数据
export default {
  setup() {
    function createData() {
      const localCount = ref(0) // 这个 ref 不会保持活跃
      return localCount
    }
    
    const count = createData()
    return { count }
  }
}

// ✅ 正确做法
export default {
  setup() {
    const count = ref(0)
    
    function createData() {
      // 在 setup 中定义的 ref 会保持活跃
      return count
    }
    
    return { count }
  }
}

高级主题和未来展望

自定义指令的实现

import { onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    // 创建自定义指令
    const focusDirective = {
      mounted(el) {
        el.focus()
      }
    }
    
    return {
      focusDirective
    }
  }
}

模块化和工程化实践

// utils/helpers.js
export const formatDate = (date) => {
  return new Date(date).toLocaleDateString()
}

export const debounce = (func, delay) => {
  let timeoutId
  return (...args) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => func.apply(this, args), delay)
  }
}

// composables/useApi.js
import { ref } from 'vue'
import { debounce } from '@/utils/helpers'

export function useApi() {
  const data = ref(null)
  const loading = ref(false)
  
  const fetchData = async (url) => {
    loading.value = true
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (error) {
      console.error('API Error:', error)
    } finally {
      loading.value = false
    }
  }
  
  return {
    data,
    loading,
    fetchData: debounce(fetchData, 300)
  }
}

总结

Vue 3 的 Composition API 为前端开发带来了革命性的变化,它提供了更加灵活和强大的组件逻辑组织方式。通过本文的详细介绍,我们涵盖了从基础概念到高级实践的各个方面:

  1. 响应式数据处理:理解 refreactive 的使用场景和最佳实践
  2. 生命周期管理:掌握各种生命周期钩子的正确使用方法
  3. 逻辑复用:学习如何创建和使用 Composables 来实现代码复用
  4. 计算属性和监听器:深入理解 computedwatch 的高级用法
  5. 组件通信:掌握父子、兄弟组件间的通信模式
  6. 性能优化:了解关键的性能优化技巧和最佳实践

通过合理使用 Composition API,我们可以编写出更加模块化、可维护和可复用的 Vue 组件。随着 Vue 生态系统的不断发展,Composition API 将继续演进,为开发者提供更强大的工具来构建现代化的前端应用。

记住,掌握 Composition API 不仅需要理解语法,更重要的是要理解其背后的思维模式。将相关的逻辑组织在一起,避免组件逻辑的碎片化,这是 Composition API 的核心理念。随着实践经验的积累,你会逐渐发现这种开发方式带来的巨大优势。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000