Vue3 Composition API实战:从基础语法到复杂组件开发完整教程

技术解码器
技术解码器 2026-03-07T00:09:10+08:00
0 0 0

前言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性彻底改变了我们编写 Vue 组件的方式,提供了更加灵活和强大的代码组织能力。本文将深入探讨 Vue3 Composition API 的核心特性,从基础语法到复杂组件开发的完整实践,帮助开发者快速掌握这一现代前端开发技术。

什么是 Composition API

核心概念

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式,它允许我们使用函数来组合和复用组件逻辑,而不是传统的选项式 API。与 Vue 2 的 Options API 相比,Composition API 提供了更灵活的代码组织方式,特别适合处理复杂的组件逻辑。

主要优势

  1. 更好的逻辑复用:通过函数封装,可以轻松在多个组件间共享逻辑
  2. 更清晰的代码结构:将相关的逻辑组织在一起,而不是分散在不同选项中
  3. 更强大的类型支持:与 TypeScript 集成更好,提供更好的开发体验
  4. 更灵活的组件开发:可以根据需要动态地添加或移除功能

基础语法详解

1. setup 函数

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

import { ref, reactive } from 'vue'

export default {
  props: {
    title: String
  },
  setup(props, context) {
    // 在这里定义响应式数据和逻辑
    const count = ref(0)
    const user = reactive({ name: 'John', age: 30 })
    
    return {
      count,
      user
    }
  }
}

2. 响应式数据

Composition API 提供了两种主要的响应式数据处理方式:refreactive

Ref 的使用

import { ref } from 'vue'

export default {
  setup() {
    // 创建基本类型的响应式数据
    const count = ref(0)
    const message = ref('Hello Vue')
    
    // 访问和修改值
    console.log(count.value) // 0
    count.value = 10
    
    return {
      count,
      message
    }
  }
}

Reactive 的使用

import { reactive } from 'vue'

export default {
  setup() {
    // 创建对象类型的响应式数据
    const state = reactive({
      count: 0,
      user: {
        name: 'John',
        age: 30
      }
    })
    
    // 直接修改属性
    state.count = 10
    state.user.name = 'Jane'
    
    return {
      state
    }
  }
}

3. 计算属性和监听器

计算属性

import { ref, computed } from 'vue'

export default {
  setup() {
    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('')
      }
    })
    
    return {
      firstName,
      lastName,
      fullName,
      reversedName
    }
  }
}

监听器

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('John')
    
    // 基础监听器
    watch(count, (newValue, oldValue) => {
      console.log(`count changed from ${oldValue} to ${newValue}`)
    })
    
    // 监听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
    })
    
    // 使用 watchEffect
    watchEffect(() => {
      console.log(`Current count is: ${count.value}`)
    })
    
    return {
      count,
      name
    }
  }
}

实际应用案例

1. 用户管理组件

让我们创建一个完整的用户管理组件来演示 Composition API 的强大功能:

<template>
  <div class="user-manager">
    <h2>用户管理</h2>
    
    <!-- 搜索和过滤 -->
    <div class="search-section">
      <input 
        v-model="searchTerm" 
        placeholder="搜索用户..." 
        class="search-input"
      />
      <select v-model="filterRole" class="role-filter">
        <option value="">所有角色</option>
        <option value="admin">管理员</option>
        <option value="user">普通用户</option>
        <option value="guest">访客</option>
      </select>
    </div>
    
    <!-- 用户列表 -->
    <div class="user-list">
      <div 
        v-for="user in filteredUsers" 
        :key="user.id" 
        class="user-item"
      >
        <div class="user-info">
          <h3>{{ user.name }}</h3>
          <p>邮箱: {{ user.email }}</p>
          <p>角色: {{ user.role }}</p>
        </div>
        <div class="user-actions">
          <button @click="editUser(user)" class="btn-edit">编辑</button>
          <button @click="deleteUser(user.id)" class="btn-delete">删除</button>
        </div>
      </div>
    </div>
    
    <!-- 分页 -->
    <div class="pagination">
      <button 
        @click="currentPage--" 
        :disabled="currentPage === 1"
        class="page-btn"
      >
        上一页
      </button>
      <span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
      <button 
        @click="currentPage++" 
        :disabled="currentPage === totalPages"
        class="page-btn"
      >
        下一页
      </button>
    </div>
    
    <!-- 添加用户表单 -->
    <div class="add-user-form">
      <h3>{{ isEditing ? '编辑用户' : '添加新用户' }}</h3>
      <form @submit.prevent="handleSubmit">
        <input 
          v-model="formData.name" 
          placeholder="姓名" 
          required
        />
        <input 
          v-model="formData.email" 
          type="email" 
          placeholder="邮箱" 
          required
        />
        <select v-model="formData.role">
          <option value="user">普通用户</option>
          <option value="admin">管理员</option>
          <option value="guest">访客</option>
        </select>
        <button type="submit">{{ isEditing ? '更新' : '添加' }}</button>
      </form>
    </div>
  </div>
</template>

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

export default {
  name: 'UserManager',
  setup() {
    // 响应式数据
    const users = ref([
      { id: 1, name: '张三', email: 'zhangsan@example.com', role: 'admin' },
      { id: 2, name: '李四', email: 'lisi@example.com', role: 'user' },
      { id: 3, name: '王五', email: 'wangwu@example.com', role: 'guest' }
    ])
    
    const searchTerm = ref('')
    const filterRole = ref('')
    const currentPage = ref(1)
    const isEditing = ref(false)
    const editingUserId = ref(null)
    
    // 表单数据
    const formData = ref({
      name: '',
      email: '',
      role: 'user'
    })
    
    // 计算属性
    const filteredUsers = computed(() => {
      let result = users.value
      
      if (searchTerm.value) {
        const term = searchTerm.value.toLowerCase()
        result = result.filter(user => 
          user.name.toLowerCase().includes(term) || 
          user.email.toLowerCase().includes(term)
        )
      }
      
      if (filterRole.value) {
        result = result.filter(user => user.role === filterRole.value)
      }
      
      return result
    })
    
    const totalPages = computed(() => {
      const itemsPerPage = 5
      return Math.ceil(filteredUsers.value.length / itemsPerPage)
    })
    
    const paginatedUsers = computed(() => {
      const itemsPerPage = 5
      const start = (currentPage.value - 1) * itemsPerPage
      const end = start + itemsPerPage
      return filteredUsers.value.slice(start, end)
    })
    
    // 监听器
    watch(currentPage, (newPage) => {
      if (newPage > totalPages.value) {
        currentPage.value = totalPages.value
      }
    })
    
    // 方法
    const editUser = (user) => {
      isEditing.value = true
      editingUserId.value = user.id
      formData.value = { ...user }
    }
    
    const deleteUser = (userId) => {
      if (confirm('确定要删除这个用户吗?')) {
        users.value = users.value.filter(user => user.id !== userId)
      }
    }
    
    const handleSubmit = () => {
      if (isEditing.value) {
        // 更新用户
        const index = users.value.findIndex(user => user.id === editingUserId.value)
        if (index !== -1) {
          users.value[index] = { ...users.value[index], ...formData.value }
        }
      } else {
        // 添加新用户
        const newUser = {
          id: Date.now(),
          ...formData.value
        }
        users.value.push(newUser)
      }
      
      // 重置表单
      formData.value = { name: '', email: '', role: 'user' }
      isEditing.value = false
      editingUserId.value = null
    }
    
    return {
      users,
      searchTerm,
      filterRole,
      currentPage,
      isEditing,
      formData,
      filteredUsers,
      totalPages,
      paginatedUsers,
      editUser,
      deleteUser,
      handleSubmit
    }
  }
}
</script>

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

.search-section {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  align-items: center;
}

.search-input, .role-filter {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.user-list {
  margin-bottom: 20px;
}

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

.user-info h3 {
  margin: 0 0 10px 0;
}

.user-actions {
  display: flex;
  gap: 10px;
}

.btn-edit, .btn-delete {
  padding: 5px 10px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.btn-edit {
  background-color: #007bff;
  color: white;
}

.btn-delete {
  background-color: #dc3545;
  color: white;
}

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}

.page-btn {
  padding: 8px 16px;
  border: 1px solid #ddd;
  background-color: white;
  cursor: pointer;
  border-radius: 4px;
}

.page-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.add-user-form {
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 4px;
}

.add-user-form form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 300px;
}

.add-user-form input, 
.add-user-form select {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.add-user-form button {
  padding: 10px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

2. 表单验证组件

<template>
  <div class="form-validator">
    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label>用户名:</label>
        <input 
          v-model="formData.username" 
          type="text"
          :class="{ 'error': errors.username }"
        />
        <span v-if="errors.username" class="error-message">{{ errors.username }}</span>
      </div>
      
      <div class="form-group">
        <label>邮箱:</label>
        <input 
          v-model="formData.email" 
          type="email"
          :class="{ 'error': errors.email }"
        />
        <span v-if="errors.email" class="error-message">{{ errors.email }}</span>
      </div>
      
      <div class="form-group">
        <label>密码:</label>
        <input 
          v-model="formData.password" 
          type="password"
          :class="{ 'error': errors.password }"
        />
        <span v-if="errors.password" class="error-message">{{ errors.password }}</span>
      </div>
      
      <div class="form-group">
        <label>确认密码:</label>
        <input 
          v-model="formData.confirmPassword" 
          type="password"
          :class="{ 'error': errors.confirmPassword }"
        />
        <span v-if="errors.confirmPassword" class="error-message">{{ errors.confirmPassword }}</span>
      </div>
      
      <button type="submit" :disabled="!isFormValid">提交</button>
    </form>
  </div>
</template>

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

export default {
  name: 'FormValidator',
  setup() {
    // 表单数据
    const formData = reactive({
      username: '',
      email: '',
      password: '',
      confirmPassword: ''
    })
    
    // 错误信息
    const errors = reactive({})
    
    // 验证规则
    const validationRules = {
      username: [
        { 
          rule: (value) => value.length >= 3, 
          message: '用户名至少需要3个字符' 
        },
        { 
          rule: (value) => /^[a-zA-Z0-9_]+$/.test(value), 
          message: '用户名只能包含字母、数字和下划线' 
        }
      ],
      email: [
        { 
          rule: (value) => value.includes('@'), 
          message: '请输入有效的邮箱地址' 
        },
        { 
          rule: (value) => value.length > 5, 
          message: '邮箱地址过短' 
        }
      ],
      password: [
        { 
          rule: (value) => value.length >= 8, 
          message: '密码至少需要8个字符' 
        },
        { 
          rule: (value) => /[A-Z]/.test(value), 
          message: '密码必须包含大写字母' 
        },
        { 
          rule: (value) => /[0-9]/.test(value), 
          message: '密码必须包含数字' 
        }
      ],
      confirmPassword: [
        { 
          rule: (value) => value === formData.password, 
          message: '两次输入的密码不一致' 
        }
      ]
    }
    
    // 验证单个字段
    const validateField = (fieldName) => {
      const fieldRules = validationRules[fieldName]
      if (!fieldRules) return
      
      for (const rule of fieldRules) {
        if (!rule.rule(formData[fieldName])) {
          errors[fieldName] = rule.message
          return
        }
      }
      
      // 如果验证通过,清除错误
      delete errors[fieldName]
    }
    
    // 验证所有字段
    const validateAll = () => {
      Object.keys(validationRules).forEach(fieldName => {
        validateField(fieldName)
      })
    }
    
    // 计算属性:表单是否有效
    const isFormValid = computed(() => {
      return Object.keys(errors).length === 0 && 
             formData.username && 
             formData.email && 
             formData.password && 
             formData.confirmPassword
    })
    
    // 监听表单数据变化并实时验证
    Object.keys(formData).forEach(fieldName => {
      const field = fieldName
      watch(() => formData[field], () => {
        validateField(field)
      })
    })
    
    // 提交处理
    const handleSubmit = () => {
      validateAll()
      
      if (isFormValid.value) {
        console.log('表单提交成功:', formData)
        alert('表单提交成功!')
      } else {
        console.log('表单验证失败')
        alert('请检查表单输入')
      }
    }
    
    return {
      formData,
      errors,
      isFormValid,
      handleSubmit
    }
  }
}
</script>

<style scoped>
.form-validator {
  max-width: 400px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.form-group {
  margin-bottom: 15px;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.form-group input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
}

.form-group input.error {
  border-color: #dc3545;
}

.error-message {
  color: #dc3545;
  font-size: 12px;
  margin-top: 5px;
  display: block;
}

button {
  width: 100%;
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

button:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
}
</style>

高级特性与最佳实践

1. 组合式函数(Composable Functions)

组合式函数是 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/useApi.js
import { ref, reactive } from 'vue'

export function useApi() {
  const loading = ref(false)
  const error = ref(null)
  const data = ref(null)
  
  const fetchData = async (url) => {
    try {
      loading.value = true
      error.value = null
      
      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
      console.error('API Error:', err)
    } finally {
      loading.value = false
    }
  }
  
  return {
    loading,
    error,
    data,
    fetchData
  }
}

2. 生命周期钩子

在 Composition API 中,生命周期钩子需要通过特定的函数来使用:

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

export default {
  setup() {
    const count = ref(0)
    
    // 挂载前
    onBeforeMount(() => {
      console.log('组件即将挂载')
    })
    
    // 挂载后
    onMounted(() => {
      console.log('组件已挂载')
      // 可以在这里执行 DOM 操作
      const timer = setInterval(() => {
        count.value++
      }, 1000)
      
      // 清理定时器
      onUnmounted(() => {
        clearInterval(timer)
      })
    })
    
    // 更新前
    onBeforeUpdate(() => {
      console.log('组件即将更新')
    })
    
    // 更新后
    onUpdated(() => {
      console.log('组件已更新')
    })
    
    // 卸载前
    onBeforeUnmount(() => {
      console.log('组件即将卸载')
    })
    
    return {
      count
    }
  }
}

3. 模板引用和 DOM 操作

<template>
  <div>
    <input ref="inputRef" v-model="message" />
    <button @click="focusInput">聚焦输入框</button>
    <p>{{ message }}</p>
  </div>
</template>

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

export default {
  setup() {
    const message = ref('')
    const inputRef = ref(null)
    
    const focusInput = () => {
      if (inputRef.value) {
        inputRef.value.focus()
      }
    }
    
    // 组件挂载后自动聚焦
    onMounted(() => {
      focusInput()
    })
    
    return {
      message,
      inputRef,
      focusInput
    }
  }
}
</script>

4. 状态管理

// stores/userStore.js
import { reactive, readonly } from 'vue'

const state = reactive({
  users: [],
  currentUser: null,
  loading: false,
  error: null
})

export function useUserStore() {
  const getUsers = async () => {
    try {
      state.loading = true
      state.error = null
      
      // 模拟 API 调用
      const response = await fetch('/api/users')
      const users = await response.json()
      
      state.users = users
    } catch (err) {
      state.error = err.message
    } finally {
      state.loading = false
    }
  }
  
  const addUser = (user) => {
    state.users.push(user)
  }
  
  const removeUser = (userId) => {
    state.users = state.users.filter(user => user.id !== userId)
  }
  
  const setCurrentUser = (user) => {
    state.currentUser = user
  }
  
  return {
    state: readonly(state),
    getUsers,
    addUser,
    removeUser,
    setCurrentUser
  }
}

性能优化技巧

1. 使用 computed 缓存

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([])
    const searchTerm = ref('')
    
    // 高性能的计算属性
    const filteredItems = computed(() => {
      if (!searchTerm.value) return items.value
      
      return items.value.filter(item => 
        item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
      )
    })
    
    // 复杂计算使用缓存
    const expensiveCalculation = computed(() => {
      // 模拟复杂的计算
      let result = 0
      for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i)
      }
      return result
    })
    
    return {
      items,
      searchTerm,
      filteredItems,
      expensiveCalculation
    }
  }
}

2. 合理使用 watch

import { ref, watch } from 'vue'

export default {
  setup() {
    const searchQuery = ref('')
    const debouncedSearch = ref('')
    
    // 使用防抖优化
    const debounce = (func, delay) => {
      let timeoutId
      return (...args) => {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => func.apply(this, args), delay)
      }
    }
    
    const debouncedWatch = debounce((newValue) => {
      debouncedSearch.value = newValue
    }, 300)
    
    watch(searchQuery, debouncedWatch)
    
    return {
      searchQuery,
      debouncedSearch
    }
  }
}

与 Vue 2 的对比

选项式 API vs Composition API

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

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

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

最佳实践总结

1. 组件结构优化

// 推荐的组件结构
import { ref, reactive, computed, watch, onMounted } from 'vue'

export default {
  name: 'OptimizedComponent',
  props: {
    // 定义 props
  },
  
  setup(props, context) {
    // 1. 响应式数据声明
    const state = reactive({
      data: [],
      loading: false,
      error: null
    })
    
    const localData = ref([])
    
    // 2. 计算属性
    const computedValue = computed(() => {
      // 计算逻辑
    })
    
    // 3. 方法定义
    const handleAction = () => {
      // 处理逻辑
    }
    
    // 4. 生命周期钩子
    onMounted(() => {
      // 挂载后逻辑
    })
    
    // 5. 监听器
    watch(() => props.someProp, (newValue, oldValue) => {
      // 监听逻辑
    })
    
    // 6. 返回需要暴露给模板的属性
    return {
      state,
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000