Vue 3 Composition API实战:从基础语法到复杂组件设计模式

健身生活志
健身生活志 2026-02-28T04:13:11+08:00
0 0 0

前言

Vue 3的发布带来了革命性的变化,其中最引人注目的就是Composition API的引入。作为Vue 3的核心特性之一,Composition API重新定义了我们编写Vue组件的方式,提供了更加灵活和强大的代码组织能力。本文将深入探讨Composition API的核心概念、基础语法,并通过丰富的实战案例展示如何运用组合式API构建可复用、可维护的Vue组件。

什么是Composition API

传统Options API的局限性

在Vue 2中,我们主要使用Options API来组织组件逻辑。这种方式虽然简单直观,但在处理复杂组件时存在一些明显的局限性:

// Vue 2 Options API示例
export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    reversedName() {
      return this.name.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}

当组件逻辑变得复杂时,Options API会导致代码分散在不同的选项中,难以维护和复用。

Composition API的优势

Composition API通过将相关的逻辑组织在一起,解决了上述问题。它允许我们以函数的形式组织组件逻辑,使得代码更加模块化和可复用。

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    const reversedName = computed(() => {
      return name.value.split('').reverse().join('')
    })
    
    const increment = () => {
      count.value++
    }
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    // 返回给模板使用的响应式数据和方法
    return {
      count,
      name,
      reversedName,
      increment
    }
  }
}

Composition API基础语法详解

响应式API基础

ref和reactive

refreactive是Composition API中最基础的两个响应式API:

import { ref, reactive } from 'vue'

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

// 使用reactive创建响应式对象
const state = reactive({
  count: 0,
  name: 'Vue'
})

// 在模板中使用时,ref需要.value访问
// 在模板中使用时,reactive直接访问

computed和watch

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

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

// 计算属性
const doubledCount = computed(() => count.value * 2)
const reversedName = computed({
  get: () => name.value.split('').reverse().join(''),
  set: (newValue) => {
    name.value = newValue.split('').reverse().join('')
  }
})

// 监听器
watch(count, (newVal, oldVal) => {
  console.log(`count从${oldVal}变为${newVal}`)
})

// 监听多个响应式数据
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
})

生命周期钩子

Composition API提供了与Vue 2相同的生命周期钩子:

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

export default {
  setup() {
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    onMounted(() => {
      console.log('组件已挂载')
    })
    
    onBeforeUpdate(() => {
      console.log('组件即将更新')
    })
    
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
    
    onUnmounted(() => {
      console.log('组件已卸载')
    })
  }
}

实战案例:构建一个完整的用户管理系统

基础组件结构

让我们通过一个完整的用户管理系统来演示Composition API的强大功能:

<template>
  <div class="user-management">
    <div class="header">
      <h2>用户管理系统</h2>
      <button @click="showAddForm = !showAddForm">
        {{ showAddForm ? '取消添加' : '添加用户' }}
      </button>
    </div>
    
    <!-- 添加用户表单 -->
    <div v-if="showAddForm" class="add-form">
      <form @submit.prevent="handleSubmit">
        <input v-model="newUser.name" placeholder="用户名" required />
        <input v-model="newUser.email" type="email" placeholder="邮箱" required />
        <input v-model="newUser.role" placeholder="角色" required />
        <button type="submit">添加用户</button>
      </form>
    </div>
    
    <!-- 用户列表 -->
    <div class="user-list">
      <div class="search-bar">
        <input v-model="searchQuery" placeholder="搜索用户..." />
        <select v-model="filterRole">
          <option value="">所有角色</option>
          <option value="admin">管理员</option>
          <option value="user">普通用户</option>
        </select>
      </div>
      
      <div class="users-grid">
        <div 
          v-for="user in filteredUsers" 
          :key="user.id" 
          class="user-card"
        >
          <h3>{{ user.name }}</h3>
          <p>{{ user.email }}</p>
          <span class="role">{{ user.role }}</span>
          <div class="actions">
            <button @click="editUser(user)">编辑</button>
            <button @click="deleteUser(user.id)">删除</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue'

export default {
  name: 'UserManagement',
  setup() {
    // 响应式数据
    const users = ref([])
    const showAddForm = ref(false)
    const newUser = ref({
      name: '',
      email: '',
      role: ''
    })
    const searchQuery = ref('')
    const filterRole = ref('')
    const editingUser = ref(null)
    
    // 计算属性
    const filteredUsers = computed(() => {
      return users.value.filter(user => {
        const matchesSearch = user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
                             user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
        const matchesRole = !filterRole.value || user.role === filterRole.value
        return matchesSearch && matchesRole
      })
    })
    
    // 方法
    const fetchUsers = async () => {
      try {
        // 模拟API调用
        const response = await fetch('/api/users')
        users.value = await response.json()
      } catch (error) {
        console.error('获取用户失败:', error)
      }
    }
    
    const handleSubmit = async () => {
      if (!newUser.value.name || !newUser.value.email || !newUser.value.role) {
        return
      }
      
      try {
        const response = await fetch('/api/users', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(newUser.value)
        })
        
        const createdUser = await response.json()
        users.value.push(createdUser)
        newUser.value = { name: '', email: '', role: '' }
        showAddForm.value = false
      } catch (error) {
        console.error('添加用户失败:', error)
      }
    }
    
    const editUser = (user) => {
      editingUser.value = { ...user }
    }
    
    const deleteUser = async (userId) => {
      if (confirm('确定要删除这个用户吗?')) {
        try {
          await fetch(`/api/users/${userId}`, {
            method: 'DELETE'
          })
          users.value = users.value.filter(user => user.id !== userId)
        } catch (error) {
          console.error('删除用户失败:', error)
        }
      }
    }
    
    // 生命周期钩子
    onMounted(() => {
      fetchUsers()
    })
    
    // 返回给模板使用的数据和方法
    return {
      users,
      showAddForm,
      newUser,
      searchQuery,
      filterRole,
      filteredUsers,
      handleSubmit,
      editUser,
      deleteUser
    }
  }
}
</script>

<style scoped>
.user-management {
  padding: 20px;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.add-form {
  margin-bottom: 20px;
  padding: 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.search-bar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.users-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}

.user-card {
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 15px;
  background: #f9f9f9;
}

.role {
  display: inline-block;
  padding: 4px 8px;
  background: #007bff;
  color: white;
  border-radius: 4px;
  font-size: 12px;
}

.actions {
  margin-top: 10px;
}

.actions button {
  margin-right: 5px;
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
</style>

高级模式:自定义组合式函数

创建可复用的组合式函数

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

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

export function useApi(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
    }
  }
  
  const postData = async (newData) => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(newData)
      })
      const result = await response.json()
      data.value = result
      return result
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }
  
  return {
    data,
    loading,
    error,
    fetchData,
    postData
  }
}

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

export function useLocalStorage(key, defaultValue) {
  const storedValue = localStorage.getItem(key)
  const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
  
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return value
}

使用自定义组合式函数

<template>
  <div class="dashboard">
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>
      <h2>用户统计</h2>
      <div class="stats">
        <div class="stat-card">
          <h3>总用户数</h3>
          <p>{{ data?.totalUsers }}</p>
        </div>
        <div class="stat-card">
          <h3>活跃用户</h3>
          <p>{{ data?.activeUsers }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { useApi } from '@/composables/useApi'
import { useLocalStorage } from '@/composables/useLocalStorage'

export default {
  setup() {
    const { data, loading, error, fetchData } = useApi('/api/dashboard')
    const theme = useLocalStorage('theme', 'light')
    
    // 组件挂载时获取数据
    fetchData()
    
    return {
      data,
      loading,
      error,
      theme
    }
  }
}
</script>

复杂组件设计模式

模态框组件模式

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

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

export default {
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: '提示'
    }
  },
  emits: ['close', 'confirm'],
  setup(props, { emit }) {
    const visible = ref(props.visible)
    
    watch(() => props.visible, (newVal) => {
      visible.value = newVal
    })
    
    const close = () => {
      visible.value = false
      emit('close')
    }
    
    const confirm = () => {
      emit('confirm')
    }
    
    const handleOverlayClick = (e) => {
      if (e.target === e.currentTarget) {
        close()
      }
    }
    
    return {
      visible,
      close,
      confirm,
      handleOverlayClick
    }
  }
}
</script>

表单验证模式

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

export function useFormValidation(initialData, rules) {
  const formData = reactive({ ...initialData })
  const errors = ref({})
  const isValid = ref(true)
  
  const validateField = (fieldName, value) => {
    const fieldRules = rules[fieldName]
    if (!fieldRules) return true
    
    for (const rule of fieldRules) {
      if (rule.required && !value) {
        errors.value[fieldName] = rule.message || `${fieldName}是必填项`
        return false
      }
      
      if (rule.pattern && !rule.pattern.test(value)) {
        errors.value[fieldName] = rule.message || `${fieldName}格式不正确`
        return false
      }
    }
    
    delete errors.value[fieldName]
    return true
  }
  
  const validateAll = () => {
    let allValid = true
    for (const field in rules) {
      if (!validateField(field, formData[field])) {
        allValid = false
      }
    }
    isValid.value = allValid
    return allValid
  }
  
  const setFieldValue = (field, value) => {
    formData[field] = value
    validateField(field, value)
  }
  
  return {
    formData,
    errors,
    isValid,
    validateAll,
    setFieldValue
  }
}

// 使用示例
export default {
  setup() {
    const initialData = {
      name: '',
      email: '',
      password: ''
    }
    
    const rules = {
      name: [
        { required: true, message: '姓名不能为空' }
      ],
      email: [
        { required: true, message: '邮箱不能为空' },
        { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }
      ],
      password: [
        { required: true, message: '密码不能为空' },
        { pattern: /^.{6,}$/, message: '密码至少6位' }
      ]
    }
    
    const { formData, errors, isValid, validateAll, setFieldValue } = useFormValidation(initialData, rules)
    
    const handleSubmit = () => {
      if (validateAll()) {
        console.log('表单提交:', formData)
      } else {
        console.log('表单验证失败')
      }
    }
    
    return {
      formData,
      errors,
      isValid,
      handleSubmit,
      setFieldValue
    }
  }
}

性能优化最佳实践

避免不必要的计算

// 不好的做法
const expensiveValue = computed(() => {
  // 复杂的计算逻辑
  return someComplexOperation(data.value)
})

// 好的做法 - 使用缓存
import { computed } from 'vue'

const expensiveValue = computed({
  get: () => {
    // 复杂的计算逻辑
    return someComplexOperation(data.value)
  },
  set: (newValue) => {
    // 设置逻辑
  }
})

合理使用watch

// 避免过度监听
const watchOptions = {
  immediate: false,  // 只在值改变时触发
  deep: false,       // 不深度监听
  flush: 'post'      // 在DOM更新后触发
}

watch(source, callback, watchOptions)

组件通信优化

<template>
  <div>
    <child-component 
      :user-data="userData" 
      @update-user="handleUserUpdate"
    />
  </div>
</template>

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

export default {
  components: {
    ChildComponent
  },
  setup() {
    const userData = ref({
      name: 'John',
      email: 'john@example.com'
    })
    
    const handleUserUpdate = (updatedData) => {
      userData.value = { ...userData.value, ...updatedData }
    }
    
    return {
      userData,
      handleUserUpdate
    }
  }
}
</script>

与Vue 2的兼容性处理

迁移指南

// Vue 2组件迁移至Vue 3
// Vue 2
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

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

export default {
  setup() {
    const count = ref(0)
    
    const increment = () => {
      count.value++
    }
    
    return {
      count,
      increment
    }
  }
}

混合使用模式

// 在Vue 3中同时使用Options API和Composition API
export default {
  name: 'MixedComponent',
  data() {
    return {
      legacyData: 'old way'
    }
  },
  setup() {
    const modernData = ref('new way')
    
    return {
      modernData
    }
  }
}

总结

Vue 3的Composition API为前端开发带来了革命性的变化。通过将相关的逻辑组织在一起,它使得代码更加模块化、可复用和易于维护。本文从基础语法到高级模式,从实战案例到性能优化,全面展示了Composition API的强大功能。

通过学习和实践Composition API,开发者可以:

  1. 提高代码复用性:通过自定义组合式函数,将通用逻辑封装成可复用的模块
  2. 增强代码可维护性:将相关的逻辑组织在一起,避免了Options API中逻辑分散的问题
  3. 提升开发效率:更灵活的代码组织方式,使得复杂组件的开发变得更加简单
  4. 改善团队协作:清晰的代码结构有助于团队成员更好地理解和维护代码

在实际项目中,建议根据具体需求选择合适的API使用方式。对于简单的组件,可以继续使用Options API;对于复杂的组件,Composition API能够提供更好的解决方案。同时,随着Vue 3生态的不断完善,我们期待看到更多基于Composition API的优秀实践和工具出现。

掌握Composition API不仅能够提升个人开发技能,更是适应现代前端开发趋势的必要能力。随着Vue生态的持续发展,Composition API必将在未来的前端开发中发挥越来越重要的作用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000