Vue 3 Composition API实战:从基础语法到复杂组件状态管理设计

黑暗骑士酱
黑暗骑士酱 2026-01-27T01:02:12+08:00
0 0 1

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比传统的 Options API,Composition API 提供了更加灵活和强大的组件开发方式,特别是在处理复杂组件逻辑时展现出了显著优势。本文将深入探讨 Vue 3 Composition API 的核心特性和使用技巧,帮助开发者构建更加灵活和可维护的 Vue 应用程序。

Vue 3 Composition API 核心概念

什么是 Composition API?

Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们通过组合函数来组织和复用组件逻辑。与传统的 Options API(如 data、methods、computed、watch 等选项)不同,Composition API 将组件的逻辑按照功能进行分组,使得代码更加模块化和可重用。

Composition API 的优势

  1. 更好的逻辑复用:通过组合函数,可以轻松地在多个组件之间共享逻辑
  2. 更灵活的代码组织:按照功能而不是选项类型来组织代码
  3. 更强的类型支持:与 TypeScript 集成更加自然
  4. 更清晰的生命周期管理:更直观地处理组件的生命周期钩子

响应式数据管理

reactive 和 ref 的基本使用

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

import { reactive, ref } from 'vue'

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

// 使用 reactive 创建响应式对象
const state = reactive({
  count: 0,
  name: 'Vue',
  userInfo: {
    age: 20,
    email: 'vue@example.com'
  }
})

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

console.log(state.count) // 0
state.count = 1

ref 的深入理解

ref 用于创建响应式的基本数据类型,当需要在模板中使用时,Vue 会自动进行解包:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

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

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

const increment = () => {
  count.value++
}
</script>

reactive 的使用场景

reactive 更适合处理复杂对象结构:

import { reactive } from 'vue'

const user = reactive({
  profile: {
    name: 'John',
    age: 30,
    address: {
      city: 'Beijing',
      country: 'China'
    }
  },
  preferences: {
    theme: 'dark',
    language: 'zh-CN'
  }
})

// 嵌套对象的响应式更新
user.profile.name = 'Jane'
user.profile.address.city = 'Shanghai'

组合函数(Composables)设计模式

创建可复用的组合函数

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

组合函数的使用示例

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">+</button>
    <button @click="counter.decrement">-</button>
    <button @click="counter.reset">Reset</button>
  </div>
</template>

<script setup>
import { useCounter } from '@/composables/useCounter'

const counter = useCounter(10)
</script>

高级组合函数示例

// 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 (error) {
      console.error('Failed to parse localStorage data:', error)
    }
  }
  
  // 监听值的变化并同步到 localStorage
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return value
}

生命周期钩子使用

在 setup 中使用生命周期钩子

<template>
  <div>{{ message }}</div>
</template>

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

const message = ref('Hello Vue!')

// 组件挂载时执行
onMounted(() => {
  console.log('Component mounted')
  // 可以在这里进行 DOM 操作或初始化
})

// 组件更新时执行
onUpdated(() => {
  console.log('Component updated')
})

// 组件卸载前执行
onUnmounted(() => {
  console.log('Component unmounted')
})
</script>

异步生命周期钩子

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

const data = ref(null)
const loading = ref(true)

// 在组件挂载后获取数据
onMounted(async () => {
  try {
    const response = await fetch('/api/data')
    data.value = await response.json()
  } catch (error) {
    console.error('Failed to fetch data:', error)
  } finally {
    loading.value = false
  }
})
</script>

计算属性和监听器

计算属性的使用

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

const firstName = ref('John')
const lastName = ref('Doe')
const age = ref(30)

// 基本计算属性
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})

// 带 getter 和 setter 的计算属性
const displayName = computed({
  get: () => {
    return `${firstName.value} ${lastName.value}`
  },
  set: (value) => {
    const names = value.split(' ')
    firstName.value = names[0]
    lastName.value = names[1]
  }
})

// 基于其他计算属性的复杂计算
const isAdult = computed(() => age.value >= 18)
const userInfo = computed(() => ({
  name: fullName.value,
  isAdult: isAdult.value
}))
</script>

监听器的使用

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

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

// 基本监听器
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(data, (newVal) => {
  console.log('Data changed:', newVal)
}, { deep: true })

// 立即执行的监听器
watch(count, (newVal) => {
  console.log('Immediate watcher:', newVal)
}, { immediate: true })

// watchEffect - 自动追踪依赖
watchEffect(() => {
  // 自动追踪 count 和 name 的变化
  console.log(`Name: ${name.value}, Count: ${count.value}`)
})

// 停止监听器
const stop = watch(count, (newVal) => {
  console.log('Watch:', newVal)
})

// 在适当的时候停止监听
// stop()
</script>

组件间通信

使用 provide 和 inject 进行跨层级通信

<!-- Parent.vue -->
<script setup>
import { provide, ref } from 'vue'

const theme = ref('dark')
const user = ref({ name: 'John', role: 'admin' })

provide('theme', theme)
provide('user', user)
provide('updateTheme', (newTheme) => {
  theme.value = newTheme
})
</script>

<template>
  <div>
    <h1>Parent Component</h1>
    <ChildComponent />
  </div>
</template>
<!-- ChildComponent.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')

const switchTheme = () => {
  updateTheme(theme.value === 'dark' ? 'light' : 'dark')
}
</script>

<template>
  <div :class="theme">
    <p>User: {{ user.name }}</p>
    <button @click="switchTheme">Switch Theme</button>
  </div>
</template>

复杂组件状态管理

状态管理组合函数设计

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

export function useUserStore() {
  // 状态
  const users = ref([])
  const loading = ref(false)
  const error = ref(null)
  
  // 计算属性
  const activeUsers = computed(() => users.value.filter(user => user.active))
  const userCount = computed(() => users.value.length)
  
  // 方法
  const fetchUsers = async () => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch('/api/users')
      if (!response.ok) {
        throw new Error('Failed to fetch users')
      }
      users.value = await response.json()
    } catch (err) {
      error.value = err.message
      console.error('Error fetching users:', err)
    } finally {
      loading.value = false
    }
  }
  
  const createUser = async (userData) => {
    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
      })
      
      if (!response.ok) {
        throw new Error('Failed to create user')
      }
      
      const newUser = await response.json()
      users.value.push(newUser)
    } catch (err) {
      error.value = err.message
      console.error('Error creating user:', err)
    }
  }
  
  const updateUser = async (userId, userData) => {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
      })
      
      if (!response.ok) {
        throw new Error('Failed to update user')
      }
      
      const updatedUser = await response.json()
      const index = users.value.findIndex(user => user.id === userId)
      if (index !== -1) {
        users.value[index] = updatedUser
      }
    } catch (err) {
      error.value = err.message
      console.error('Error updating user:', err)
    }
  }
  
  const deleteUser = async (userId) => {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        method: 'DELETE'
      })
      
      if (!response.ok) {
        throw new Error('Failed to delete user')
      }
      
      users.value = users.value.filter(user => user.id !== userId)
    } catch (err) {
      error.value = err.message
      console.error('Error deleting user:', err)
    }
  }
  
  return {
    // 状态
    users,
    loading,
    error,
    
    // 计算属性
    activeUsers,
    userCount,
    
    // 方法
    fetchUsers,
    createUser,
    updateUser,
    deleteUser
  }
}

在组件中使用状态管理组合函数

<template>
  <div class="user-management">
    <h2>User Management</h2>
    
    <!-- 加载状态 -->
    <div v-if="loading" class="loading">Loading...</div>
    
    <!-- 错误处理 -->
    <div v-if="error" class="error">{{ error }}</div>
    
    <!-- 用户列表 -->
    <div v-else>
      <button @click="fetchUsers">Refresh Users</button>
      <ul>
        <li v-for="user in users" :key="user.id">
          {{ user.name }} - {{ user.email }}
          <button @click="deleteUser(user.id)">Delete</button>
        </li>
      </ul>
      
      <!-- 创建新用户表单 -->
      <form @submit.prevent="createNewUser">
        <input v-model="newUser.name" placeholder="Name" required />
        <input v-model="newUser.email" placeholder="Email" required />
        <button type="submit">Create User</button>
      </form>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/composables/useUserStore'

const { users, loading, error, fetchUsers, createUser, deleteUser } = useUserStore()

// 新用户表单数据
const newUser = ref({
  name: '',
  email: ''
})

// 创建新用户
const createNewUser = async () => {
  if (newUser.value.name && newUser.value.email) {
    await createUser(newUser.value)
    newUser.value = { name: '', email: '' }
  }
}

// 组件挂载时获取用户数据
fetchUsers()
</script>

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

.loading, .error {
  padding: 10px;
  margin: 10px 0;
}

.error {
  color: red;
  background-color: #ffebee;
}

button {
  margin: 5px;
  padding: 8px 16px;
}
</style>

性能优化技巧

使用 memoization 优化计算属性

// composables/useMemo.js
import { computed } from 'vue'

export function useMemo(fn, deps) {
  return computed(() => {
    // 这里可以实现自定义的缓存逻辑
    return fn()
  })
}

// 使用示例
export function useExpensiveCalculation(data) {
  const expensiveResult = computed(() => {
    // 模拟一个昂贵的计算过程
    console.log('Performing expensive calculation...')
    return data.value.map(item => item * 2)
  })
  
  return {
    expensiveResult
  }
}

避免不必要的重新渲染

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

// 使用 shallowRef 只监听顶层属性变化
const shallowData = shallowRef({
  name: 'Vue',
  version: '3.0'
})

// 只有当顶层属性改变时才会触发更新
watch(shallowData, (newVal) => {
  console.log('Shallow data changed:', newVal)
})

// 使用 toRaw 避免响应式包装
const rawData = ref({ name: 'Vue' })
const rawObject = toRaw(rawData.value)

// 在需要时使用 reactive 和 ref 的选择
const normalRef = ref({ a: 1, b: 2 })
const reactiveObj = reactive({ a: 1, b: 2 })
</script>

最佳实践和注意事项

组合函数命名规范

// ✅ 推荐的命名方式
export function useCounter() { }
export function useUserStore() { }
export function useApiService() { }
export function useLocalStorage() { }

// ❌ 不推荐的命名方式
export function counter() { }
export function userStore() { }

组合函数的文档化

/**
 * 用户认证状态管理组合函数
 * @description 提供用户登录、登出和认证状态检查功能
 * @returns {Object} 包含认证状态和操作方法的对象
 */
export function useAuth() {
  const isAuthenticated = ref(false)
  const user = ref(null)
  const loading = ref(false)
  
  /**
   * 用户登录
   * @param {string} username - 用户名
   * @param {string} password - 密码
   * @returns {Promise<boolean>} 登录结果
   */
  const login = async (username, password) => {
    // 实现登录逻辑
  }
  
  /**
   * 用户登出
   */
  const logout = () => {
    // 实现登出逻辑
  }
  
  return {
    isAuthenticated,
    user,
    loading,
    login,
    logout
  }
}

错误处理和边界情况

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

const data = ref(null)
const error = ref(null)
const loading = ref(false)

// 使用 try-catch 处理异步操作
const fetchData = async () => {
  try {
    loading.value = true
    error.value = null
    
    // 模拟 API 调用
    const response = await fetch('/api/data')
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    
    data.value = await response.json()
  } catch (err) {
    error.value = err.message
    console.error('Failed to fetch data:', err)
  } finally {
    loading.value = false
  }
}

// 监听数据变化并处理错误
watch(data, (newData) => {
  if (newData && Array.isArray(newData)) {
    // 处理数组数据
  }
}, { deep: true })
</script>

总结

Vue 3 Composition API 为前端开发带来了更加灵活和强大的组件开发方式。通过本文的介绍,我们了解了:

  1. 响应式数据管理:掌握了 refreactive 的基本用法和最佳实践
  2. 组合函数设计:学习了如何创建可复用的逻辑模块
  3. 生命周期管理:理解了如何在 Composition API 中使用生命周期钩子
  4. 复杂状态管理:通过实际示例展示了如何构建完整的状态管理解决方案
  5. 性能优化:了解了提高应用性能的关键技巧

Composition API 的核心价值在于它提供了更好的代码组织方式和逻辑复用能力,特别是在处理复杂组件时能够显著提升代码的可维护性和可读性。随着 Vue 3 的普及,掌握 Composition API 将成为现代前端开发的重要技能。

在实际项目中,建议根据具体需求选择合适的 API 方式,对于简单的组件可以继续使用 Options API,而对于复杂的业务逻辑,则应该优先考虑使用 Composition API 来组织代码。同时,合理的组合函数设计和良好的命名规范将帮助团队更好地协作和维护代码质量。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000