Vue 3 Composition API实战:组件通信、状态管理与性能优化全攻略

Nora590
Nora590 2026-01-31T08:09:19+08:00
0 0 1

引言

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 的基础是几个核心的响应式函数:

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

// 创建响应式数据
const count = ref(0)
const user = reactive({ name: 'John', age: 25 })

// 计算属性
const doubleCount = computed(() => count.value * 2)

// 侦听器
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

setup 函数

setup 是 Composition API 的入口函数,所有逻辑都写在这里:

import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = reactive({ name: 'John', age: 25 })
    
    return {
      count,
      user
    }
  }
}

组件间通信模式

Props 传递数据

在 Composition API 中,props 的使用方式与 Options API 略有不同:

// 子组件
import { computed } from 'vue'

export default {
  props: {
    title: String,
    user: Object
  },
  setup(props) {
    // props 是响应式的
    const displayName = computed(() => {
      return props.user?.name || 'Anonymous'
    })
    
    return {
      displayName
    }
  }
}

emit 事件传递

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

export default {
  emits: ['update:count', 'submit'],
  setup(props, { emit }) {
    const count = ref(0)
    
    const handleIncrement = () => {
      count.value++
      emit('update:count', count.value)
    }
    
    const handleSubmit = (data) => {
      emit('submit', data)
    }
    
    return {
      count,
      handleIncrement,
      handleSubmit
    }
  }
}

provide/inject 依赖注入

// 父组件
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')
    
    return {
      theme,
      user
    }
  }
}

全局状态管理

对于复杂应用,可以使用 provide/inject 实现简单的全局状态管理:

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

export const store = reactive({
  state: {
    user: null,
    theme: 'light',
    notifications: []
  },
  
  setUser(user) {
    this.state.user = user
  },
  
  setTheme(theme) {
    this.state.theme = theme
  },
  
  addNotification(notification) {
    this.state.notifications.push(notification)
  }
})

export const useStore = () => {
  return readonly(store)
}

// main.js
import { createApp } from 'vue'
import { store } from './store'

const app = createApp(App)
app.provide('store', store)

// 组件中使用
import { inject } from 'vue'

export default {
  setup() {
    const store = inject('store')
    
    const login = (userData) => {
      store.setUser(userData)
    }
    
    return {
      login,
      user: computed(() => store.state.user)
    }
  }
}

响应式数据管理

ref vs reactive

理解 ref 和 reactive 的区别至关重要:

import { ref, reactive } from 'vue'

// ref 用于基本类型和对象的包装
const count = ref(0) // 等价于 { value: 0 }
const name = ref('John') // 等价于 { value: 'John' }

// reactive 用于创建响应式对象
const user = reactive({
  name: 'John',
  age: 25,
  address: {
    city: 'Beijing',
    country: 'China'
  }
})

// 使用时的区别
console.log(count.value) // 0
console.log(user.name) // John

深层响应式处理

import { reactive, watch } from 'vue'

const state = reactive({
  user: {
    profile: {
      name: 'John',
      settings: {
        theme: 'dark',
        notifications: true
      }
    }
  }
})

// 监听深层变化
watch(
  () => state.user.profile.settings,
  (newSettings, oldSettings) => {
    console.log('Settings changed:', newSettings)
  },
  { deep: true }
)

// 或者使用 watchEffect
watchEffect(() => {
  console.log(state.user.profile.name)
})

响应式数据的性能优化

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

export default {
  setup() {
    const items = ref([])
    const searchTerm = ref('')
    
    // 使用 computed 缓存计算结果
    const filteredItems = computed(() => {
      return items.value.filter(item => 
        item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
      )
    })
    
    // 使用 watchEffect 优化副作用
    watchEffect(() => {
      // 只在依赖变化时执行
      console.log(`Found ${filteredItems.value.length} items`)
    })
    
    return {
      items,
      searchTerm,
      filteredItems
    }
  }
}

计算属性与侦听器详解

计算属性的高级用法

import { computed, ref } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    const age = ref(25)
    
    // 基础计算属性
    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 userStats = computed(() => {
      return {
        name: fullName.value,
        isAdult: age.value >= 18,
        yearsUntilRetirement: Math.max(0, 65 - age.value)
      }
    })
    
    return {
      firstName,
      lastName,
      age,
      fullName,
      displayName,
      userStats
    }
  }
}

侦听器的灵活应用

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('John')
    const items = ref([])
    
    // 基础侦听器
    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`)
    })
    
    // 侦听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`Count: ${newCount}, Name: ${newName}`)
    })
    
    // 深度侦听
    watch(items, (newItems) => {
      console.log('Items array changed:', newItems)
    }, { deep: true })
    
    // 立即执行的侦听器
    watch(count, (newValue) => {
      console.log('Immediate watcher:', newValue)
    }, { immediate: true })
    
    // 侦听器清理函数
    const stopWatcher = watch(count, (newValue) => {
      // 模拟异步操作
      const timer = setTimeout(() => {
        console.log('Processed:', newValue)
      }, 1000)
      
      return () => {
        clearTimeout(timer)
      }
    })
    
    // watchEffect - 自动追踪依赖
    watchEffect(() => {
      console.log(`Name: ${name.value}, Count: ${count.value}`)
      // 会自动追踪 name 和 count 的变化
    })
    
    return {
      count,
      name,
      items,
      stopWatcher
    }
  }
}

高级侦听器模式

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

export default {
  setup() {
    const searchQuery = ref('')
    const results = ref([])
    const loading = ref(false)
    
    // 防抖搜索
    const debouncedSearch = (query) => {
      if (!query) {
        results.value = []
        return
      }
      
      loading.value = true
      
      // 模拟 API 调用
      setTimeout(() => {
        results.value = [
          { id: 1, name: `${query} result 1` },
          { id: 2, name: `${query} result 2` }
        ]
        loading.value = false
      }, 300)
    }
    
    // 使用 watchEffect 实现防抖
    watchEffect(() => {
      if (searchQuery.value) {
        debouncedSearch(searchQuery.value)
      } else {
        results.value = []
        loading.value = false
      }
    })
    
    // 节流操作
    const throttledSave = throttle((data) => {
      console.log('Saving data:', data)
      // 实际的保存逻辑
    }, 1000)
    
    function throttle(func, limit) {
      let inThrottle
      return function() {
        const args = arguments
        const context = this
        if (!inThrottle) {
          func.apply(context, args)
          inThrottle = true
          setTimeout(() => inThrottle = false, limit)
        }
      }
    }
    
    return {
      searchQuery,
      results,
      loading,
      throttledSave
    }
  }
}

性能优化策略

组件渲染优化

import { ref, computed, shallowRef, markRaw } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const expensiveData = ref(null)
    
    // 使用 shallowRef 浅层响应式,避免深层递归追踪
    const shallowData = shallowRef({
      name: 'John',
      items: [1, 2, 3]
    })
    
    // 对于不需要响应式的对象,使用 markRaw
    const nonReactiveObject = markRaw({
      id: 1,
      name: 'Non-reactive'
    })
    
    // 计算属性优化
    const expensiveComputation = computed(() => {
      // 复杂计算,使用缓存避免重复执行
      return Array.from({ length: 1000 }, (_, i) => i * i)
    })
    
    // 使用 memoization 缓存函数结果
    const memoizedFunction = (input) => {
      if (!memoizedFunction.cache) {
        memoizedFunction.cache = new Map()
      }
      
      if (memoizedFunction.cache.has(input)) {
        return memoizedFunction.cache.get(input)
      }
      
      const result = expensiveComputation.value[input] || 0
      memoizedFunction.cache.set(input, result)
      return result
    }
    
    return {
      count,
      expensiveData,
      shallowData,
      nonReactiveObject,
      expensiveComputation,
      memoizedFunction
    }
  }
}

渲染性能优化

<template>
  <div>
    <!-- 使用 v-memo 提高性能 -->
    <div v-for="item in items" :key="item.id">
      <div v-memo="[item.name, item.value]">
        {{ item.name }}: {{ item.value }}
      </div>
    </div>
    
    <!-- 条件渲染优化 -->
    <div v-if="showDetails">
      <component :is="dynamicComponent" />
    </div>
    
    <!-- 列表渲染优化 -->
    <div v-for="(item, index) in optimizedList" :key="index">
      {{ item }}
    </div>
  </div>
</template>

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

export default {
  setup() {
    const items = ref([])
    const showDetails = ref(false)
    const dynamicComponent = ref(null)
    
    // 使用计算属性优化列表
    const optimizedList = computed(() => {
      return items.value.slice(0, 100) // 只渲染前100项
    })
    
    // 避免不必要的重新渲染
    const expensiveComputed = computed(() => {
      // 只在依赖变化时计算
      return items.value.reduce((acc, item) => acc + item.value, 0)
    })
    
    return {
      items,
      showDetails,
      dynamicComponent,
      optimizedList,
      expensiveComputed
    }
  }
}
</script>

异步数据处理优化

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

export default {
  setup() {
    const data = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    // 使用 async/await 处理异步操作
    const fetchData = async () => {
      try {
        loading.value = true
        error.value = null
        
        // 模拟 API 调用
        const response = await fetch('/api/data')
        const result = await response.json()
        
        data.value = result
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 组件卸载时取消请求
    let abortController = null
    
    const fetchWithAbort = async () => {
      if (abortController) {
        abortController.abort()
      }
      
      abortController = new AbortController()
      
      try {
        loading.value = true
        error.value = null
        
        const response = await fetch('/api/data', {
          signal: abortController.signal
        })
        
        if (!response.ok) {
          throw new Error('Network response was not ok')
        }
        
        const result = await response.json()
        data.value = result
      } catch (err) {
        if (err.name !== 'AbortError') {
          error.value = err.message
        }
      } finally {
        loading.value = false
      }
    }
    
    // 组件挂载时获取数据
    onMounted(() => {
      fetchData()
    })
    
    // 组件卸载时清理
    onUnmounted(() => {
      if (abortController) {
        abortController.abort()
      }
    })
    
    return {
      data,
      loading,
      error,
      fetchData,
      fetchWithAbort
    }
  }
}

实际应用案例

复杂表单管理

<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <label>姓名</label>
      <input v-model="formData.name" type="text" />
    </div>
    
    <div class="form-group">
      <label>邮箱</label>
      <input v-model="formData.email" type="email" />
    </div>
    
    <div class="form-group">
      <label>年龄</label>
      <input v-model.number="formData.age" type="number" />
    </div>
    
    <button type="submit" :disabled="isSubmitting">提交</button>
    <button type="button" @click="resetForm">重置</button>
  </form>
  
  <div v-if="validationErrors.length > 0">
    <ul>
      <li v-for="error in validationErrors" :key="error">{{ error }}</li>
    </ul>
  </div>
</template>

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

export default {
  setup() {
    const formData = reactive({
      name: '',
      email: '',
      age: null
    })
    
    const isSubmitting = ref(false)
    const validationErrors = ref([])
    
    // 表单验证规则
    const validateForm = computed(() => {
      return {
        name: !formData.name || formData.name.length < 2,
        email: !formData.email || !isValidEmail(formData.email),
        age: formData.age === null || formData.age < 0 || formData.age > 150
      }
    })
    
    // 验证邮箱格式
    const isValidEmail = (email) => {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      return emailRegex.test(email)
    }
    
    // 实时验证
    watch(formData, () => {
      validationErrors.value = []
      
      if (validateForm.value.name) {
        validationErrors.value.push('姓名至少需要2个字符')
      }
      
      if (validateForm.value.email) {
        validationErrors.value.push('请输入有效的邮箱地址')
      }
      
      if (validateForm.value.age) {
        validationErrors.value.push('年龄必须在0-150之间')
      }
    }, { deep: true })
    
    // 表单提交
    const handleSubmit = async () => {
      if (validationErrors.value.length > 0) {
        return
      }
      
      isSubmitting.value = true
      
      try {
        await submitForm(formData)
        console.log('表单提交成功')
      } catch (error) {
        console.error('表单提交失败:', error)
      } finally {
        isSubmitting.value = false
      }
    }
    
    // 重置表单
    const resetForm = () => {
      Object.assign(formData, {
        name: '',
        email: '',
        age: null
      })
      validationErrors.value = []
    }
    
    // 模拟表单提交
    const submitForm = (data) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() > 0.2) {
            resolve(data)
          } else {
            reject(new Error('网络错误'))
          }
        }, 1000)
      })
    }
    
    return {
      formData,
      isSubmitting,
      validationErrors,
      handleSubmit,
      resetForm
    }
  }
}
</script>

数据表格组件

<template>
  <div class="data-table">
    <div class="table-header">
      <input 
        v-model="searchQuery" 
        placeholder="搜索..." 
        class="search-input"
      />
      
      <select v-model="sortBy" @change="sortData">
        <option value="name">按姓名排序</option>
        <option value="age">按年龄排序</option>
        <option value="email">按邮箱排序</option>
      </select>
    </div>
    
    <table>
      <thead>
        <tr>
          <th @click="sort('name')">姓名</th>
          <th @click="sort('age')">年龄</th>
          <th @click="sort('email')">邮箱</th>
          <th>操作</th>
        </tr>
      </thead>
      
      <tbody>
        <tr v-for="row in paginatedData" :key="row.id">
          <td>{{ row.name }}</td>
          <td>{{ row.age }}</td>
          <td>{{ row.email }}</td>
          <td>
            <button @click="editRow(row)">编辑</button>
            <button @click="deleteRow(row.id)">删除</button>
          </td>
        </tr>
      </tbody>
    </table>
    
    <div class="pagination">
      <button 
        :disabled="currentPage === 1" 
        @click="goToPage(currentPage - 1)"
      >
        上一页
      </button>
      
      <span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
      
      <button 
        :disabled="currentPage === totalPages" 
        @click="goToPage(currentPage + 1)"
      >
        下一页
      </button>
    </div>
    
    <div v-if="loading">加载中...</div>
  </div>
</template>

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

export default {
  props: {
    data: {
      type: Array,
      required: true
    }
  },
  
  setup(props) {
    const searchQuery = ref('')
    const sortBy = ref('name')
    const sortOrder = ref('asc')
    const currentPage = ref(1)
    const pageSize = ref(10)
    
    const loading = ref(false)
    
    // 搜索和排序后的数据
    const filteredAndSortedData = computed(() => {
      let result = [...props.data]
      
      // 搜索过滤
      if (searchQuery.value) {
        const query = searchQuery.value.toLowerCase()
        result = result.filter(item => 
          item.name.toLowerCase().includes(query) ||
          item.email.toLowerCase().includes(query)
        )
      }
      
      // 排序
      result.sort((a, b) => {
        const aVal = a[sortBy.value]
        const bVal = b[sortBy.value]
        
        if (sortOrder.value === 'asc') {
          return aVal > bVal ? 1 : -1
        } else {
          return aVal < bVal ? 1 : -1
        }
      })
      
      return result
    })
    
    // 分页数据
    const paginatedData = computed(() => {
      const start = (currentPage.value - 1) * pageSize.value
      const end = start + pageSize.value
      return filteredAndSortedData.value.slice(start, end)
    })
    
    const totalPages = computed(() => {
      return Math.ceil(filteredAndSortedData.value.length / pageSize.value)
    })
    
    // 排序方法
    const sort = (field) => {
      if (sortBy.value === field) {
        sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
      } else {
        sortBy.value = field
        sortOrder.value = 'asc'
      }
    }
    
    // 分页导航
    const goToPage = (page) => {
      if (page >= 1 && page <= totalPages.value) {
        currentPage.value = page
      }
    }
    
    // 数据排序和搜索实时更新
    watchEffect(() => {
      // 模拟异步加载数据
      loading.value = true
      setTimeout(() => {
        loading.value = false
      }, 500)
    })
    
    const editRow = (row) => {
      console.log('编辑行:', row)
    }
    
    const deleteRow = (id) => {
      console.log('删除行 ID:', id)
    }
    
    // 排序数据
    const sortData = () => {
      // 排序逻辑已通过 computed 实现
    }
    
    return {
      searchQuery,
      sortBy,
      sortOrder,
      currentPage,
      pageSize,
      loading,
      paginatedData,
      totalPages,
      sort,
      goToPage,
      editRow,
      deleteRow,
      sortData
    }
  }
}
</script>

<style scoped>
.data-table {
  padding: 20px;
}

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

.search-input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 20px;
  margin-top: 20px;
}
</style>

最佳实践与注意事项

代码组织原则

// 好的组织方式:按功能分组
import { ref, reactive, computed, watch } from 'vue'

export default {
  setup() {
    // 1. 响应式数据声明
    const count = ref(0)
    const user = reactive({ name: '', email: '' })
    
    // 2. 计算属性
    const displayName = computed(() => {
      return user.name || 'Anonymous'
    })
    
    const isAdult = computed(() => {
      return user.age >= 18
    })
    
    // 3. 本地方法
    const increment = () => {
      count.value++
    }
    
    const reset = () => {
      count.value = 0
    }
    
    // 4. 异步操作
    const fetchData = async () => {
      // 异步逻辑
    }
    
    // 5. 监听器
    watch(count, (newValue) => {
      console.log('Count changed:', newValue)
    })
    
    return {
      count,
      user,
      displayName,
      isAdult,
      increment,
      reset,
      fetchData
    }
  }
}

性能监控和调试

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

export default {
  setup() {
    const startTime = performance.now()
    
    // 使用计算属性时的性能监控
    const expensiveValue = computed(() => {
      console.log('Computing expensive value...')
      // 模拟复杂计算
      let result = 0
      for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i)
      }
      return result
    })
    
    // 监听性能变化
    watch(expensiveValue, (newValue) => {
      const endTime = performance.now()
      console.log(`Computed value took ${(endTime - startTime).toFixed(2)}ms`)
    })
    
    // 使用 Vue DevTools 的调试工具
    if (__VUE_DEVTOOLS__) {
      console.log('Vue DevTools detected')
    }
    
    return {
      expensiveValue
    }
  }
}

总结

Vue 3 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还极大地增强了组件间的通信能力和状态管理能力。通过合理使用 refreactivecomputedwatch 等核心函数,开发者可以构建出更加模块化、可维护和高性能的应用程序。

在实际开发中,建议遵循以下原则:

  1. 合理的代码组织:将相关的逻辑分组,便于维护和复用
  2. 性能优化意识:合理使用计算属性和侦听器,避免不必要的重复计算
  3. **组件通信最佳实践
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000