Vue 3 Composition API实战指南:从基础语法到复杂组件重构

Oliver248
Oliver248 2026-02-05T00:13:11+08:00
0 0 1

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。作为 Vue 3 的核心特性之一,Composition API 为开发者提供了更加灵活和强大的组件开发方式。它不仅解决了 Options API 在大型项目中面临的代码组织问题,还大大提升了代码的复用性和可维护性。

在本文中,我们将深入探讨 Composition API 的各个方面,从基础语法到高级应用,从简单组件到复杂重构,帮助开发者全面掌握这一重要技术。无论你是刚刚接触 Vue 3 的新手,还是正在考虑将现有项目迁移到 Composition API 的资深开发者,本文都将为你提供实用的指导和最佳实践。

Vue 3 Composition API 核心概念

什么是 Composition API

Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许开发者通过组合函数来组织和复用逻辑代码。与传统的 Options API 不同,Composition API 将组件的逻辑按功能进行分组,而不是按照选项类型进行分组。

这种设计模式使得开发者可以更灵活地组织代码,特别是在处理复杂组件时,能够更好地管理状态、计算属性和方法等。

Composition API 的优势

  1. 更好的逻辑复用:通过组合函数,可以轻松地在多个组件之间共享逻辑
  2. 更强的类型支持:与 TypeScript 集成更好,提供更准确的类型推断
  3. 更灵活的代码组织:按照功能而不是选项类型来组织代码
  4. 更好的性能:减少了不必要的副作用和计算
  5. 更容易测试:逻辑可以独立于组件进行测试

响应式 API 详解

reactive 和 ref 的基本使用

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

import { reactive, ref } from 'vue'

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

// 使用 reactive 创建响应式对象
const state = reactive({
  name: 'John',
  age: 25,
  hobbies: ['reading', 'coding']
})

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

console.log(state.name) // John
state.name = 'Jane'

reactive vs ref 的选择

选择使用 ref 还是 reactive 主要取决于数据的类型:

// 基本类型数据使用 ref
const count = ref(0)
const name = ref('Vue')

// 对象类型数据使用 reactive
const user = reactive({
  name: 'John',
  age: 25,
  address: {
    city: 'Beijing',
    country: 'China'
  }
})

// 但是需要注意,如果对象中的属性是基本类型,需要手动包装
const state = reactive({
  count: ref(0), // 这样可以保持响应性
  name: ref('Vue')
})

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 reversedName = computed({
  get: () => {
    return firstName.value.split('').reverse().join('')
  },
  set: (value) => {
    firstName.value = value.split('').reverse().join('')
  }
})

watch 监听器

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

import { ref, watch } from 'vue'

const count = ref(0)
const message = ref('Hello')

// 监听单个响应式变量
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

// 监听多个响应式变量
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
  console.log(`count: ${oldCount} -> ${newCount}, message: ${oldMessage} -> ${newMessage}`)
})

// 深度监听对象
const user = reactive({
  name: 'John',
  profile: {
    age: 25
  }
})

watch(
  () => user.profile,
  (newProfile, oldProfile) => {
    console.log('profile changed')
  },
  { deep: true }
)

生命周期钩子

组件生命周期的使用

在 Composition API 中,生命周期钩子通过对应的函数来调用:

import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } 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
    }
  }
}

组件通信

父子组件通信

在 Composition API 中,父子组件通信依然遵循 Vue 的标准模式:

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

export default {
  setup() {
    const parentMessage = ref('Hello from parent')
    
    const handleChildEvent = (message) => {
      console.log('Received from child:', message)
    }

    return {
      parentMessage,
      handleChildEvent
    }
  }
}
<!-- 父组件模板 -->
<template>
  <div>
    <child-component 
      :message="parentMessage" 
      @child-event="handleChildEvent"
    />
  </div>
</template>
// 子组件
export default {
  props: ['message'],
  emits: ['child-event'],
  setup(props, { emit }) {
    const childMessage = ref('Hello from child')
    
    const sendMessageToParent = () => {
      emit('child-event', childMessage.value)
    }
    
    return {
      sendMessageToParent
    }
  }
}

provide 和 inject

provideinject 是 Composition API 中用于跨层级组件通信的重要工具:

// 祖先组件
import { provide, ref } from 'vue'

export default {
  setup() {
    const theme = ref('dark')
    const user = ref({ name: 'John', role: 'admin' })
    
    provide('theme', theme)
    provide('user', user)
    
    return {
      theme,
      user
    }
  }
}
// 后代组件
import { inject } from 'vue'

export default {
  setup() {
    const theme = inject('theme')
    const user = inject('user')
    
    // 使用注入的数据
    console.log(theme.value) // dark
    console.log(user.value.name) // John
    
    return {
      theme,
      user
    }
  }
}

组合函数(Composables)开发

组合函数的基本概念

组合函数是 Vue 3 Composition API 中的核心概念之一,它是一个封装了可复用逻辑的函数。组合函数以 use 开头命名,遵循统一的命名规范。

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

使用组合函数

import { useCounter } from './composables/useCounter'

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

高级组合函数示例

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

export function useLocalStorage(key, defaultValue) {
  const value = ref(defaultValue)
  
  // 从 localStorage 初始化值
  try {
    const stored = localStorage.getItem(key)
    if (stored) {
      value.value = JSON.parse(stored)
    }
  } catch (error) {
    console.error('Failed to initialize from localStorage:', error)
  }
  
  // 监听值变化并保存到 localStorage
  watch(value, (newValue) => {
    try {
      localStorage.setItem(key, JSON.stringify(newValue))
    } catch (error) {
      console.error('Failed to save to localStorage:', error)
    }
  }, { deep: true })
  
  return value
}

从 Options API 迁移到 Composition API

基本迁移策略

将现有组件从 Options API 迁移到 Composition API 需要遵循一定的步骤:

// Options API 组件
export default {
  name: 'UserCard',
  props: ['user'],
  data() {
    return {
      loading: false,
      error: null
    }
  },
  computed: {
    displayName() {
      return `${this.user.firstName} ${this.user.lastName}`
    },
    isAdult() {
      return this.user.age >= 18
    }
  },
  methods: {
    async fetchUserData() {
      this.loading = true
      try {
        const response = await fetch(`/api/users/${this.user.id}`)
        this.user = await response.json()
      } catch (err) {
        this.error = err.message
      } finally {
        this.loading = false
      }
    },
    reset() {
      this.$emit('reset')
    }
  },
  mounted() {
    this.fetchUserData()
  }
}
// Composition API 组件
import { ref, computed, onMounted } from 'vue'

export default {
  name: 'UserCard',
  props: ['user'],
  setup(props, { emit }) {
    const loading = ref(false)
    const error = ref(null)
    
    const displayName = computed(() => {
      return `${props.user.firstName} ${props.user.lastName}`
    })
    
    const isAdult = computed(() => {
      return props.user.age >= 18
    })
    
    const fetchUserData = async () => {
      loading.value = true
      try {
        const response = await fetch(`/api/users/${props.user.id}`)
        // 注意:这里需要重新赋值给 props 或者使用 reactive
        // 实际应用中可能需要其他处理方式
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    const reset = () => {
      emit('reset')
    }
    
    onMounted(() => {
      fetchUserData()
    })
    
    return {
      loading,
      error,
      displayName,
      isAdult,
      reset
    }
  }
}

复杂组件的迁移示例

// 原始 Options API 组件
export default {
  name: 'ProductList',
  data() {
    return {
      products: [],
      loading: false,
      error: null,
      searchQuery: '',
      currentPage: 1,
      itemsPerPage: 10
    }
  },
  computed: {
    filteredProducts() {
      return this.products.filter(product => 
        product.name.toLowerCase().includes(this.searchQuery.toLowerCase())
      )
    },
    paginatedProducts() {
      const start = (this.currentPage - 1) * this.itemsPerPage
      return this.filteredProducts.slice(start, start + this.itemsPerPage)
    },
    totalPages() {
      return Math.ceil(this.filteredProducts.length / this.itemsPerPage)
    }
  },
  methods: {
    async fetchProducts() {
      this.loading = true
      try {
        const response = await fetch('/api/products')
        this.products = await response.json()
      } catch (err) {
        this.error = err.message
      } finally {
        this.loading = false
      }
    },
    handlePageChange(page) {
      this.currentPage = page
    },
    handleSearch(query) {
      this.searchQuery = query
      this.currentPage = 1
    }
  },
  mounted() {
    this.fetchProducts()
  }
}
// Composition API 组件
import { ref, computed, onMounted } from 'vue'

export default {
  name: 'ProductList',
  setup() {
    const products = ref([])
    const loading = ref(false)
    const error = ref(null)
    const searchQuery = ref('')
    const currentPage = ref(1)
    const itemsPerPage = ref(10)
    
    const filteredProducts = computed(() => {
      return products.value.filter(product => 
        product.name.toLowerCase().includes(searchQuery.value.toLowerCase())
      )
    })
    
    const paginatedProducts = computed(() => {
      const start = (currentPage.value - 1) * itemsPerPage.value
      return filteredProducts.value.slice(start, start + itemsPerPage.value)
    })
    
    const totalPages = computed(() => {
      return Math.ceil(filteredProducts.value.length / itemsPerPage.value)
    })
    
    const fetchProducts = async () => {
      loading.value = true
      try {
        const response = await fetch('/api/products')
        products.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    const handlePageChange = (page) => {
      currentPage.value = page
    }
    
    const handleSearch = (query) => {
      searchQuery.value = query
      currentPage.value = 1
    }
    
    onMounted(() => {
      fetchProducts()
    })
    
    return {
      products,
      loading,
      error,
      searchQuery,
      currentPage,
      itemsPerPage,
      filteredProducts,
      paginatedProducts,
      totalPages,
      handlePageChange,
      handleSearch
    }
  }
}

性能优化技巧

合理使用响应式 API

// 不好的做法:过度使用 reactive
const badExample = reactive({
  name: '',
  age: 0,
  email: '',
  phone: '',
  address: {
    street: '',
    city: '',
    country: ''
  },
  hobbies: [],
  skills: []
})

// 好的做法:按需使用 ref 和 reactive
const name = ref('')
const age = ref(0)
const email = ref('')
const phone = ref('')
const address = reactive({
  street: '',
  city: '',
  country: ''
})
const hobbies = ref([])
const skills = ref([])

避免不必要的计算

// 不好的做法:在每次渲染时都进行复杂计算
export default {
  setup() {
    const items = ref([])
    
    // 这种方式会在每次渲染时重新计算
    const expensiveCalculation = computed(() => {
      return items.value.reduce((acc, item) => {
        // 复杂的计算逻辑
        return acc + item.value * 2
      }, 0)
    })
    
    return {
      expensiveCalculation
    }
  }
}

// 好的做法:使用 watch 和缓存机制
export default {
  setup() {
    const items = ref([])
    const cachedResult = ref(null)
    
    watch(items, () => {
      // 只在 items 变化时重新计算
      cachedResult.value = items.value.reduce((acc, item) => {
        return acc + item.value * 2
      }, 0)
    }, { immediate: true })
    
    return {
      cachedResult
    }
  }
}

组件级别的优化

import { shallowRef, markRaw } from 'vue'

export default {
  setup() {
    // 使用 shallowRef 只响应顶层属性变化
    const shallowData = shallowRef({
      name: 'John',
      profile: {
        age: 25
      }
    })
    
    // 使用 markRaw 避免对象被转为响应式
    const rawObject = markRaw({
      method: function() {
        return 'raw method'
      }
    })
    
    return {
      shallowData,
      rawObject
    }
  }
}

最佳实践和注意事项

组合函数命名规范

// 好的命名方式
export function useApi() { /* ... */ }
export function useAuth() { /* ... */ }
export function useStorage() { /* ... */ }
export function useValidation() { /* ... */ }

// 不好的命名方式
export function api() { /* ... */ }
export function auth() { /* ... */ }
export function storage() { /* ... */ }

错误处理和边界情况

import { ref, watch } from 'vue'

export function useAsyncData(fetcher) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const execute = async (...args) => {
    loading.value = true
    error.value = null
    
    try {
      data.value = await fetcher(...args)
    } catch (err) {
      error.value = err
      console.error('Async data fetching failed:', err)
    } finally {
      loading.value = false
    }
  }
  
  // 立即执行
  if (typeof fetcher === 'function') {
    execute()
  }
  
  return {
    data,
    loading,
    error,
    execute
  }
}

TypeScript 支持

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

interface User {
  id: number
  name: string
  email: string
}

export function useUser(): {
  user: Ref<User | null>
  loading: Ref<boolean>
  error: Ref<string | null>
  fetchUser: (id: number) => Promise<void>
} {
  const user = ref<User | null>(null)
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  const fetchUser = async (id: number) => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(`/api/users/${id}`)
      user.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  return {
    user,
    loading,
    error,
    fetchUser
  }
}

实际项目案例

完整的购物车组件示例

<template>
  <div class="cart">
    <h2>购物车</h2>
    
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <div 
        v-for="item in cartItems" 
        :key="item.id"
        class="cart-item"
      >
        <span>{{ item.name }}</span>
        <span>数量: {{ item.quantity }}</span>
        <span>小计: ¥{{ item.price * item.quantity }}</span>
        <button @click="removeItem(item.id)">删除</button>
      </div>
      
      <div class="cart-summary">
        <p>总数量: {{ totalItems }}</p>
        <p>总价: ¥{{ totalPrice }}</p>
        <button @click="checkout">结算</button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import { useLocalStorage } from './composables/useLocalStorage'

export default {
  name: 'ShoppingCart',
  setup() {
    const cartItems = useLocalStorage('cartItems', [])
    const loading = ref(false)
    const error = ref(null)
    
    // 计算属性
    const totalItems = computed(() => {
      return cartItems.value.reduce((total, item) => total + item.quantity, 0)
    })
    
    const totalPrice = computed(() => {
      return cartItems.value.reduce((total, item) => 
        total + (item.price * item.quantity), 0
      )
    })
    
    // 方法
    const removeItem = (id) => {
      cartItems.value = cartItems.value.filter(item => item.id !== id)
    }
    
    const checkout = async () => {
      loading.value = true
      error.value = null
      
      try {
        await fetch('/api/checkout', {
          method: 'POST',
          body: JSON.stringify({
            items: cartItems.value,
            total: totalPrice.value
          })
        })
        
        // 清空购物车
        cartItems.value = []
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    return {
      cartItems,
      loading,
      error,
      totalItems,
      totalPrice,
      removeItem,
      checkout
    }
  }
}
</script>

<style scoped>
.cart {
  padding: 20px;
}

.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.cart-summary {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 2px solid #eee;
}
</style>

总结

Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅解决了传统 Options API 在大型项目中的局限性,还提供了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们了解了:

  1. 响应式 API 的核心概念和使用方法,包括 refreactivecomputedwatch
  2. 生命周期钩子 的正确使用方式
  3. 组件通信 的多种实现方案
  4. 组合函数 的开发模式和最佳实践
  5. 从 Options API 迁移 的具体步骤和注意事项
  6. 性能优化 的关键技巧

掌握 Composition API 不仅能够提升代码质量,还能显著改善团队协作效率。在实际项目中,建议根据具体需求选择合适的 API 使用方式,并遵循一致的命名规范和编码风格。

随着 Vue 3 生态系统的不断完善,Composition API 将成为现代 Vue 开发的标准模式。持续学习和实践是掌握这一技术的关键,希望本文能够为你的 Vue 3 开发之旅提供有价值的指导和帮助。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000