Vue 3 Composition API实战:从基础到高级的组件开发模式演进

WildUlysses
WildUlysses 2026-01-29T13:15:01+08:00
0 0 0

引言

Vue 3的发布标志着前端开发进入了一个新的时代。作为Vue.js框架的重大升级,Vue 3引入了Composition API这一革命性的特性,彻底改变了我们编写组件的方式。与传统的Options API相比,Composition API提供了更灵活、更强大的组件逻辑组织方式,特别是在处理复杂业务逻辑和组件复用方面展现出巨大优势。

本文将深入剖析Vue 3 Composition API的核心概念和使用技巧,通过大量实际案例演示组件逻辑复用、响应式数据管理、生命周期钩子等高级特性。无论您是刚接触Vue 3的新手开发者,还是希望提升现有技能的资深工程师,都能从本文中获得实用的知识和最佳实践。

Vue 3 Composition API基础概念

什么是Composition API?

Composition API是Vue 3中引入的一种新的组件逻辑组织方式。它允许我们将组件的相关逻辑按功能进行分组,而不是按照选项类型(如data、methods、computed等)来组织代码。这种设计模式使得代码更加模块化、可复用,并且更容易维护。

在传统的Options API中,我们通常将数据、方法、计算属性等分散在不同的选项中:

export default {
  data() {
    return {
      count: 0,
      name: ''
    }
  },
  computed: {
    fullName() {
      return `${this.name} ${this.count}`
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}

而使用Composition API,我们可以将相关的逻辑组合在一起:

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('')
    
    const fullName = computed(() => `${name.value} ${count.value}`)
    
    const increment = () => {
      count.value++
    }
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    return {
      count,
      name,
      fullName,
      increment
    }
  }
}

setup函数的作用

setup是Composition API的核心入口点。它在组件实例创建之前执行,接收两个参数:props和context。

export default {
  props: ['title'],
  setup(props, context) {
    // props包含父组件传递的属性
    console.log(props.title)
    
    // context包含组件上下文信息
    console.log(context.attrs)
    console.log(context.emit)
    console.log(context.slots)
    
    return {}
  }
}

响式数据管理详解

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
    })
    
    // 访问ref值时需要使用.value
    console.log(count.value) // 0
    
    // 修改ref值
    count.value = 10
    
    // reactive对象可以直接访问属性
    console.log(user.firstName) // John
    
    // 修改reactive对象属性
    user.age = 31
    
    return {
      count,
      name,
      user
    }
  }
}

深层响应式数据处理

对于嵌套的对象和数组,Vue 3提供了深层响应式支持:

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: {
        profile: {
          name: 'Alice',
          email: 'alice@example.com'
        },
        preferences: ['email', 'sms']
      }
    })
    
    // 深层响应式数据可以正常工作
    const updateUserEmail = () => {
      state.user.profile.email = 'newemail@example.com'
    }
    
    const addPreference = () => {
      state.user.preferences.push('push')
    }
    
    return {
      state,
      updateUserEmail,
      addPreference
    }
  }
}

computed计算属性

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 reversedName = computed({
      get: () => {
        return firstName.value.split('').reverse().join('')
      },
      set: (newValue) => {
        firstName.value = newValue.split('').reverse().join('')
      }
    })
    
    // 复杂计算属性
    const userSummary = computed(() => {
      return `${fullName.value} (${age.value} years old)`
    })
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      reversedName,
      userSummary
    }
  }
}

组件生命周期钩子

生命周期的使用方式

Composition API中的生命周期钩子与Options API保持一致,但使用方式更加灵活:

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

export default {
  setup() {
    const count = ref(0)
    const message = ref('')
    
    // 组件挂载时执行
    onMounted(() => {
      console.log('组件已挂载')
      fetchData()
    })
    
    // 组件更新时执行
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    // 组件卸载前执行
    onUnmounted(() => {
      console.log('组件即将卸载')
      // 清理定时器等资源
    })
    
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data')
        const data = await response.json()
        message.value = data.message
      } catch (error) {
        console.error('数据获取失败:', error)
      }
    }
    
    return {
      count,
      message
    }
  }
}

异步生命周期处理

在实际开发中,经常需要处理异步操作的生命周期:

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

export default {
  setup() {
    const data = ref(null)
    const loading = ref(false)
    const error = ref(null)
    let timer = null
    
    // 异步数据获取
    const fetchData = async () => {
      loading.value = true
      error.value = null
      
      try {
        const response = await fetch('/api/users')
        const result = await response.json()
        data.value = result
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 定时刷新数据
    const startAutoRefresh = () => {
      timer = setInterval(() => {
        fetchData()
      }, 5000)
    }
    
    const stopAutoRefresh = () => {
      if (timer) {
        clearInterval(timer)
        timer = null
      }
    }
    
    onMounted(() => {
      fetchData()
      startAutoRefresh()
    })
    
    onUnmounted(() => {
      stopAutoRefresh()
    })
    
    return {
      data,
      loading,
      error
    }
  }
}

组件逻辑复用与组合函数

创建可复用的组合函数

组合函数是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
  }
}

// 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 () => {
    if (!url.value) return
    
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url.value)
      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
    } finally {
      loading.value = false
    }
  }
  
  watch(url, fetchData)
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}

使用组合函数的组件示例

<template>
  <div>
    <h2>计数器示例</h2>
    <p>当前计数: {{ count }}</p>
    <p>双倍计数: {{ doubleCount }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <button @click="reset">重置</button>
    
    <h2>数据获取示例</h2>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else-if="data">
      <pre>{{ JSON.stringify(data, null, 2) }}</pre>
    </div>
    <button @click="refetch">刷新数据</button>
  </div>
</template>

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

export default {
  setup() {
    // 使用计数器组合函数
    const { count, increment, decrement, reset, doubleCount } = useCounter(10)
    
    // 使用数据获取组合函数
    const url = ref('/api/users')
    const { data, loading, error, refetch } = useFetch(url)
    
    return {
      count,
      increment,
      decrement,
      reset,
      doubleCount,
      data,
      loading,
      error,
      refetch
    }
  }
}
</script>

复杂组合函数示例

让我们创建一个更复杂的组合函数来处理表单验证:

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

export function useForm(initialValues = {}) {
  const form = reactive({ ...initialValues })
  const errors = ref({})
  const isSubmitting = ref(false)
  
  // 验证规则
  const rules = {
    email: (value) => {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      return emailRegex.test(value) || '请输入有效的邮箱地址'
    },
    required: (value) => {
      return value !== null && value !== undefined && value !== '' || '此字段为必填项'
    },
    minLength: (value, min) => {
      return value.length >= min || `至少需要${min}个字符`
    }
  }
  
  // 验证单个字段
  const validateField = (field, value) => {
    const fieldRules = form.rules?.[field]
    if (!fieldRules) return null
    
    for (const rule of fieldRules) {
      if (typeof rule === 'function') {
        const result = rule(value)
        if (result !== true) return result
      } else {
        const [validator, ...params] = rule.split(':')
        const result = rules[validator](value, ...params.map(p => parseInt(p)))
        if (result !== true) return result
      }
    }
    
    return null
  }
  
  // 验证所有字段
  const validateForm = () => {
    const newErrors = {}
    let isValid = true
    
    Object.keys(form).forEach(key => {
      if (key === 'rules') return
      
      const value = form[key]
      const error = validateField(key, value)
      
      if (error) {
        newErrors[key] = error
        isValid = false
      }
    })
    
    errors.value = newErrors
    return isValid
  }
  
  // 设置字段值并验证
  const setFieldValue = (field, value) => {
    form[field] = value
    const error = validateField(field, value)
    if (error) {
      errors.value[field] = error
    } else {
      delete errors.value[field]
    }
  }
  
  // 提交表单
  const submitForm = async (submitHandler) => {
    if (!validateForm()) return false
    
    isSubmitting.value = true
    
    try {
      const result = await submitHandler(form)
      return result
    } catch (error) {
      console.error('表单提交失败:', error)
      throw error
    } finally {
      isSubmitting.value = false
    }
  }
  
  // 重置表单
  const resetForm = () => {
    Object.keys(initialValues).forEach(key => {
      form[key] = initialValues[key]
    })
    errors.value = {}
  }
  
  // 计算属性:是否所有字段都有效
  const isValid = computed(() => {
    return Object.keys(errors.value).length === 0
  })
  
  // 计算属性:表单数据
  const formData = computed(() => {
    const data = {}
    Object.keys(form).forEach(key => {
      if (key !== 'rules') {
        data[key] = form[key]
      }
    })
    return data
  })
  
  return {
    form,
    errors,
    isSubmitting,
    isValid,
    formData,
    setFieldValue,
    validateForm,
    submitForm,
    resetForm
  }
}

高级特性与最佳实践

事件处理和自定义事件

在Composition API中,可以更灵活地处理组件间的通信:

<template>
  <div>
    <h3>用户信息</h3>
    <p>用户名: {{ user.name }}</p>
    <p>邮箱: {{ user.email }}</p>
    
    <button @click="updateUserInfo">更新用户信息</button>
    <button @click="emitCustomEvent">触发自定义事件</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  props: ['user'],
  emits: ['user-updated', 'custom-event'],
  setup(props, { emit }) {
    const updateUserInfo = () => {
      // 模拟更新用户信息
      emit('user-updated', {
        ...props.user,
        lastUpdated: new Date().toISOString()
      })
    }
    
    const emitCustomEvent = () => {
      emit('custom-event', {
        type: 'user-action',
        timestamp: Date.now()
      })
    }
    
    return {
      updateUserInfo,
      emitCustomEvent
    }
  }
}
</script>

插槽和内容分发

Composition API中的插槽处理与Options API类似,但提供了更多的灵活性:

<template>
  <div class="card">
    <header v-if="$slots.header">
      <slot name="header"></slot>
    </header>
    
    <main>
      <slot></slot>
    </main>
    
    <footer v-if="$slots.footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<script>
export default {
  setup(props, { slots }) {
    // 可以在setup中访问slots
    console.log('Slots:', slots)
    
    return {}
  }
}
</script>

性能优化策略

在使用Composition API时,需要注意性能优化:

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

export default {
  setup() {
    const count = ref(0)
    const items = ref([])
    
    // 使用computed缓存计算结果
    const expensiveCalculation = computed(() => {
      // 复杂计算逻辑
      return items.value.reduce((sum, item) => sum + item.value, 0)
    })
    
    // 合理使用watch,避免不必要的监听
    watch(count, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    })
    
    // 使用深监听时要谨慎
    watch(items, (newItems) => {
      console.log('Items updated:', newItems)
    }, { deep: true })
    
    // 在合适的生命周期钩子中执行操作
    onMounted(() => {
      // 组件挂载后执行初始化逻辑
      fetchInitialData()
    })
    
    const fetchInitialData = async () => {
      // 异步数据获取
    }
    
    return {
      count,
      items,
      expensiveCalculation
    }
  }
}

实际项目应用案例

电商购物车组件

让我们通过一个完整的实际案例来演示Composition API的应用:

<template>
  <div class="shopping-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">
        <img :src="item.image" :alt="item.name" width="50" />
        <div class="item-info">
          <h4>{{ item.name }}</h4>
          <p>单价: ¥{{ item.price }}</p>
          <p>数量: {{ item.quantity }}</p>
        </div>
        <div class="item-actions">
          <button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
          <span>{{ item.quantity }}</span>
          <button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
          <button @click="removeItem(item.id)">删除</button>
        </div>
      </div>
      
      <div class="cart-summary">
        <p>总计: ¥{{ totalPrice }}</p>
        <button 
          @click="checkout" 
          :disabled="cartItems.length === 0 || isCheckoutLoading"
        >
          {{ isCheckoutLoading ? '支付中...' : '去结算' }}
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue'
import { useFetch } from '@/composables/useFetch'

export default {
  setup() {
    const cartItems = ref([])
    const loading = ref(false)
    const error = ref(null)
    const isCheckoutLoading = ref(false)
    
    // 获取购物车数据
    const fetchCartData = async () => {
      loading.value = true
      error.value = null
      
      try {
        const response = await fetch('/api/cart')
        const data = await response.json()
        cartItems.value = data.items || []
      } catch (err) {
        error.value = '获取购物车数据失败'
        console.error(err)
      } finally {
        loading.value = false
      }
    }
    
    // 更新商品数量
    const updateQuantity = async (id, quantity) => {
      if (quantity <= 0) {
        removeItem(id)
        return
      }
      
      try {
        await fetch(`/api/cart/${id}`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ quantity })
        })
        
        const item = cartItems.value.find(item => item.id === id)
        if (item) {
          item.quantity = quantity
        }
      } catch (err) {
        console.error('更新数量失败:', err)
      }
    }
    
    // 删除商品
    const removeItem = async (id) => {
      try {
        await fetch(`/api/cart/${id}`, {
          method: 'DELETE'
        })
        
        cartItems.value = cartItems.value.filter(item => item.id !== id)
      } catch (err) {
        console.error('删除商品失败:', err)
      }
    }
    
    // 结算
    const checkout = async () => {
      if (cartItems.value.length === 0) return
      
      isCheckoutLoading.value = true
      
      try {
        await fetch('/api/checkout', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            items: cartItems.value.map(item => ({
              id: item.id,
              quantity: item.quantity
            }))
          })
        })
        
        // 结算成功后清空购物车
        cartItems.value = []
        alert('结算成功!')
      } catch (err) {
        console.error('结算失败:', err)
        alert('结算失败,请重试')
      } finally {
        isCheckoutLoading.value = false
      }
    }
    
    // 计算总价
    const totalPrice = computed(() => {
      return cartItems.value.reduce((total, item) => {
        return total + (item.price * item.quantity)
      }, 0)
    })
    
    onMounted(() => {
      fetchCartData()
    })
    
    return {
      cartItems,
      loading,
      error,
      isCheckoutLoading,
      totalPrice,
      updateQuantity,
      removeItem,
      checkout
    }
  }
}
</script>

<style scoped>
.shopping-cart {
  max-width: 800px;
  margin: 0 auto;
}

.cart-item {
  display: flex;
  align-items: center;
  padding: 16px;
  border: 1px solid #ddd;
  margin-bottom: 16px;
  border-radius: 8px;
}

.item-info {
  flex: 1;
  margin: 0 16px;
}

.item-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.item-actions button {
  padding: 4px 8px;
  border: none;
  background: #007bff;
  color: white;
  border-radius: 4px;
  cursor: pointer;
}

.item-actions button:hover {
  background: #0056b3;
}

.cart-summary {
  margin-top: 24px;
  padding: 16px;
  border: 1px solid #ddd;
  border-radius: 8px;
  text-align: right;
}

.cart-summary button {
  padding: 12px 24px;
  background: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.cart-summary button:disabled {
  background: #ccc;
  cursor: not-allowed;
}
</style>

数据可视化组件

另一个实用的案例是创建一个数据可视化组件:

<template>
  <div class="chart-container">
    <h3>{{ title }}</h3>
    <div ref="chartRef" class="chart"></div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'

export default {
  props: {
    title: String,
    apiUrl: String,
    chartType: {
      type: String,
      default: 'bar'
    }
  },
  setup(props) {
    const chartRef = ref(null)
    const chartInstance = ref(null)
    const loading = ref(false)
    const error = ref(null)
    
    // 初始化图表
    const initChart = () => {
      if (chartRef.value) {
        chartInstance.value = echarts.init(chartRef.value)
        
        // 设置默认配置
        chartInstance.value.setOption({
          tooltip: {
            trigger: 'axis'
          },
          legend: {
            data: []
          }
        })
      }
    }
    
    // 更新图表数据
    const updateChart = (data) => {
      if (!chartInstance.value) return
      
      const option = {
        xAxis: {
          type: 'category',
          data: data.categories || []
        },
        yAxis: {
          type: 'value'
        },
        series: [{
          name: props.title,
          type: props.chartType,
          data: data.values || [],
          itemStyle: {
            color: '#409EFF'
          }
        }]
      }
      
      chartInstance.value.setOption(option, true)
    }
    
    // 获取数据
    const fetchData = async () => {
      if (!props.apiUrl) return
      
      loading.value = true
      error.value = null
      
      try {
        const response = await fetch(props.apiUrl)
        const data = await response.json()
        updateChart(data)
      } catch (err) {
        error.value = '数据获取失败'
        console.error(err)
      } finally {
        loading.value = false
      }
    }
    
    // 响应式调整图表大小
    const resizeChart = () => {
      if (chartInstance.value) {
        chartInstance.value.resize()
      }
    }
    
    onMounted(() => {
      initChart()
      fetchData()
      
      // 监听窗口大小变化
      window.addEventListener('resize', resizeChart)
    })
    
    onUnmounted(() => {
      if (chartInstance.value) {
        chartInstance.value.dispose()
      }
      window.removeEventListener('resize', resizeChart)
    })
    
    return {
      chartRef,
      loading,
      error
    }
  }
}
</script>

<style scoped>
.chart-container {
  padding: 16px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.chart {
  width: 100%;
  height: 400px;
}
</style>

总结与展望

Vue 3的Composition API为前端开发带来了革命性的变化,它不仅提供了更灵活的组件逻辑组织方式,还大大增强了代码的可复用性和维护性。通过本文的详细介绍,我们可以看到:

  1. 更好的代码组织:将相关的逻辑组合在一起,避免了Options API中逻辑分散的问题
  2. 强大的组合函数:通过自定义组合函数实现逻辑复用,提高开发效率
  3. 灵活的生命周期管理:更精确地控制组件的生命周期行为
  4. 优秀的响应式系统refreactive提供了完善的响应式数据处理能力

在实际项目中,建议根据具体需求选择合适的API使用方式。对于简单的组件,可以继续使用Options API;而对于复杂的业务逻辑,Composition API的优势将得到充分体现。

随着Vue生态的不断发展,我们期待看到更多基于Composition API的优秀工具和库出现。同时,Vue 3的TypeScript支持也为开发者的代码质量提供了更好的保障。

掌握Vue 3 Composition API不仅是技术能力的提升,更是

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000