Vue 3 Composition API实战:从组件通信到状态管理的完整应用体系

HotCat
HotCat 2026-01-30T21:19:04+08:00
0 0 0

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更加灵活和强大的开发方式,特别是在处理复杂组件逻辑时展现出了巨大优势。本文将深入探讨 Vue 3 Composition API 的设计理念和使用方法,并通过构建完整的应用案例来展示如何在实际项目中运用这些技术。

Vue 3 Composition API 核心概念

什么是 Composition API?

Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式,它允许开发者以函数的形式组织组件逻辑,而不是传统的选项式结构。这种方式使得代码更加灵活,更容易复用和维护。

主要优势

  1. 更好的逻辑复用:通过组合式函数(Composable Functions)实现逻辑的复用
  2. 更清晰的代码组织:将相关的逻辑组织在一起,而不是分散在不同的选项中
  3. 更强的类型支持:与 TypeScript 集成更好,提供更好的开发体验
  4. 更灵活的组件设计:可以更自由地组织和管理组件状态

响应式系统详解

reactive 和 ref 的使用

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

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

// 使用 reactive 创建响应式对象
const state = reactive({
  user: {
    name: 'John',
    age: 30
  },
  posts: []
})

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

computed 的高级用法

import { ref, computed } from 'vue'

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('')
  }
})

组件通信实战

父子组件通信

父组件向子组件传递数据

<!-- Parent.vue -->
<template>
  <div class="parent">
    <h2>父组件</h2>
    <Child 
      :message="parentMessage" 
      :user-info="userInfo"
      @child-event="handleChildEvent"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const parentMessage = ref('Hello from parent')
const userInfo = ref({
  name: 'Alice',
  age: 25
})

const handleChildEvent = (data) => {
  console.log('收到子组件事件:', data)
}
</script>
<!-- Child.vue -->
<template>
  <div class="child">
    <h3>子组件</h3>
    <p>{{ message }}</p>
    <p>用户信息: {{ userInfo.name }} - {{ userInfo.age }}</p>
    <button @click="sendToParent">发送消息给父组件</button>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

// 定义 props
const props = defineProps({
  message: {
    type: String,
    default: ''
  },
  userInfo: {
    type: Object,
    required: true
  }
})

// 定义 emits
const emit = defineEmits(['child-event'])

const sendToParent = () => {
  emit('child-event', {
    message: 'Hello from child',
    timestamp: Date.now()
  })
}
</script>

兄弟组件通信

<!-- ComponentA.vue -->
<template>
  <div class="component-a">
    <h3>组件 A</h3>
    <input v-model="message" placeholder="输入消息" />
    <button @click="sendMessage">发送消息</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useSharedState } from '../composables/useSharedState'

const message = ref('')
const { sharedMessage, setSharedMessage } = useSharedState()

const sendMessage = () => {
  setSharedMessage(message.value)
}
</script>
<!-- ComponentB.vue -->
<template>
  <div class="component-b">
    <h3>组件 B</h3>
    <p>接收到的消息: {{ sharedMessage }}</p>
  </div>
</template>

<script setup>
import { useSharedState } from '../composables/useSharedState'

const { sharedMessage } = useSharedState()
</script>

组合式函数复用

创建可复用的组合式函数

// 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
}
// 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)
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  return {
    loading,
    error,
    data,
    fetchData
  }
}
// composables/useSharedState.js
import { ref } from 'vue'

const sharedMessage = ref('')
const sharedUser = ref(null)

export function useSharedState() {
  const setSharedMessage = (message) => {
    sharedMessage.value = message
  }

  const setSharedUser = (user) => {
    sharedUser.value = user
  }

  return {
    sharedMessage,
    sharedUser,
    setSharedMessage,
    setSharedUser
  }
}

使用组合式函数

<!-- UserProfile.vue -->
<template>
  <div class="user-profile">
    <h2>用户信息</h2>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else-if="data">
      <p>姓名: {{ data.name }}</p>
      <p>邮箱: {{ data.email }}</p>
      <p>年龄: {{ data.age }}</p>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { useApi } from '../composables/useApi'

const { loading, error, data, fetchData } = useApi()

onMounted(() => {
  fetchData('/api/user/1')
})
</script>
<!-- ThemeSwitcher.vue -->
<template>
  <div class="theme-switcher">
    <button @click="toggleTheme">切换主题</button>
    <p>当前主题: {{ theme }}</p>
  </div>
</template>

<script setup>
import { useLocalStorage } from '../composables/useLocalStorage'

const theme = useLocalStorage('theme', 'light')

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

状态管理实践

简单的状态管理器

// store/userStore.js
import { ref, computed } from 'vue'

const currentUser = ref(null)
const isLoggedIn = ref(false)
const userPreferences = ref({})

export function useUserStore() {
  const login = (userData) => {
    currentUser.value = userData
    isLoggedIn.value = true
  }

  const logout = () => {
    currentUser.value = null
    isLoggedIn.value = false
  }

  const updatePreferences = (preferences) => {
    userPreferences.value = { ...userPreferences.value, ...preferences }
  }

  const getUserInfo = computed(() => ({
    user: currentUser.value,
    isAuthenticated: isLoggedIn.value,
    preferences: userPreferences.value
  }))

  return {
    login,
    logout,
    updatePreferences,
    getUserInfo
  }
}
<!-- Login.vue -->
<template>
  <div class="login">
    <form @submit.prevent="handleLogin">
      <input v-model="username" placeholder="用户名" />
      <input v-model="password" type="password" placeholder="密码" />
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useUserStore } from '../store/userStore'

const username = ref('')
const password = ref('')
const { login } = useUserStore()

const handleLogin = async () => {
  try {
    // 模拟 API 调用
    const userData = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username: username.value, password: password.value })
    }).then(res => res.json())
    
    login(userData)
    // 跳转到主页或其他页面
  } catch (error) {
    console.error('登录失败:', error)
  }
}
</script>

复杂状态管理示例

// store/appStore.js
import { ref, computed, watch } from 'vue'

const state = ref({
  loading: false,
  error: null,
  data: [],
  filters: {
    category: '',
    search: '',
    sortBy: 'name'
  },
  pagination: {
    page: 1,
    limit: 10,
    total: 0
  }
})

export function useAppStore() {
  // 获取数据
  const fetchData = async () => {
    state.value.loading = true
    state.value.error = null
    
    try {
      const response = await fetch('/api/data', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json'
        }
      })
      
      const result = await response.json()
      state.value.data = result.data
      state.value.pagination.total = result.total
    } catch (error) {
      state.value.error = error.message
    } finally {
      state.value.loading = false
    }
  }

  // 更新过滤器
  const updateFilter = (key, value) => {
    state.value.filters[key] = value
    state.value.pagination.page = 1
  }

  // 更新分页
  const updatePagination = (page) => {
    state.value.pagination.page = page
  }

  // 重置过滤器
  const resetFilters = () => {
    state.value.filters = {
      category: '',
      search: '',
      sortBy: 'name'
    }
    state.value.pagination.page = 1
  }

  // 计算属性
  const filteredData = computed(() => {
    let result = [...state.value.data]
    
    // 应用搜索过滤
    if (state.value.filters.search) {
      const searchLower = state.value.filters.search.toLowerCase()
      result = result.filter(item => 
        item.name.toLowerCase().includes(searchLower) ||
        item.description.toLowerCase().includes(searchLower)
      )
    }
    
    // 应用分类过滤
    if (state.value.filters.category) {
      result = result.filter(item => item.category === state.value.filters.category)
    }
    
    // 排序
    if (state.value.filters.sortBy) {
      result.sort((a, b) => {
        if (a[state.value.filters.sortBy] < b[state.value.filters.sortBy]) return -1
        if (a[state.value.filters.sortBy] > b[state.value.filters.sortBy]) return 1
        return 0
      })
    }
    
    return result
  })

  const paginatedData = computed(() => {
    const start = (state.value.pagination.page - 1) * state.value.pagination.limit
    const end = start + state.value.pagination.limit
    return filteredData.value.slice(start, end)
  })

  // 监听过滤器变化,自动重新获取数据
  watch(
    () => state.value.filters,
    () => {
      fetchData()
    },
    { deep: true }
  )

  return {
    state: computed(() => state.value),
    fetchData,
    updateFilter,
    updatePagination,
    resetFilters,
    filteredData,
    paginatedData
  }
}

高级技巧和最佳实践

条件渲染和动态组件

<!-- DynamicComponent.vue -->
<template>
  <div class="dynamic-component">
    <div class="tabs">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        @click="activeTab = tab.name"
        :class="{ active: activeTab === tab.name }"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <component 
      :is="currentComponent" 
      v-bind="currentProps"
      @update-data="handleDataUpdate"
    />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const tabs = [
  { name: 'componentA', label: '组件 A' },
  { name: 'componentB', label: '组件 B' }
]

const activeTab = ref('componentA')
const componentData = ref({})

const currentComponent = computed(() => {
  return activeTab.value === 'componentA' ? ComponentA : ComponentB
})

const currentProps = computed(() => {
  return {
    data: componentData.value,
    ...activeTab.value === 'componentA' ? { title: '组件 A 标题' } : { title: '组件 B 标题' }
  }
})

const handleDataUpdate = (data) => {
  componentData.value = data
}
</script>

错误处理和加载状态

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

export function useAsync(asyncFunction) {
  const loading = ref(false)
  const error = ref(null)
  const data = ref(null)
  const executed = ref(false)

  const execute = async (...args) => {
    try {
      loading.value = true
      error.value = null
      data.value = await asyncFunction(...args)
      executed.value = true
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  const reset = () => {
    loading.value = false
    error.value = null
    data.value = null
    executed.value = false
  }

  return {
    loading,
    error,
    data,
    executed,
    execute,
    reset
  }
}

性能优化技巧

<!-- OptimizedComponent.vue -->
<template>
  <div class="optimized-component">
    <div v-for="item in items" :key="item.id" class="item">
      <span>{{ item.name }}</span>
      <button @click="handleItemClick(item)">操作</button>
    </div>
  </div>
</template>

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

// 使用 shallowRef 优化对象引用
const items = shallowRef([])

// 使用计算属性缓存复杂计算
const processedItems = computed(() => {
  return items.value.map(item => ({
    ...item,
    processed: item.name.toUpperCase()
  }))
})

// 监听特定属性变化
watch(
  () => items.value.length,
  (newLength, oldLength) => {
    console.log(`项目数量从 ${oldLength} 变为 ${newLength}`)
  }
)

const handleItemClick = (item) => {
  // 避免不必要的重新渲染
  console.log('点击项目:', item.id)
}
</script>

实际应用案例

完整的博客管理系统

<!-- BlogApp.vue -->
<template>
  <div class="blog-app">
    <header class="app-header">
      <h1>我的博客</h1>
      <nav>
        <router-link to="/">首页</router-link>
        <router-link to="/admin">管理</router-link>
      </nav>
    </header>

    <main class="app-main">
      <router-view />
    </main>

    <footer class="app-footer">
      <p>&copy; 2023 我的博客</p>
    </footer>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { useUserStore } from './store/userStore'

const { getUserInfo } = useUserStore()

onMounted(() => {
  // 初始化用户状态
  console.log('应用初始化')
})
</script>
<!-- BlogList.vue -->
<template>
  <div class="blog-list">
    <div class="controls">
      <input v-model="searchQuery" placeholder="搜索文章..." />
      <select v-model="selectedCategory">
        <option value="">所有分类</option>
        <option v-for="category in categories" :key="category" :value="category">
          {{ category }}
        </option>
      </select>
    </div>

    <div class="articles">
      <article 
        v-for="article in paginatedArticles" 
        :key="article.id"
        class="article-card"
      >
        <h2>{{ article.title }}</h2>
        <p class="meta">
          {{ formatDate(article.createdAt) }} | 
          {{ article.category }}
        </p>
        <p class="excerpt">{{ article.excerpt }}</p>
        <router-link :to="`/article/${article.id}`" class="read-more">
          阅读更多
        </router-link>
      </article>
    </div>

    <div class="pagination">
      <button 
        @click="currentPage--" 
        :disabled="currentPage === 1"
      >
        上一页
      </button>
      <span>第 {{ currentPage }} 页</span>
      <button 
        @click="currentPage++" 
        :disabled="currentPage === totalPages"
      >
        下一页
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch } from 'vue'
import { useAppStore } from '../store/appStore'

const searchQuery = ref('')
const selectedCategory = ref('')
const currentPage = ref(1)

const { 
  state: appState, 
  fetchData, 
  updateFilter,
  updatePagination 
} = useAppStore()

// 获取分类列表
const categories = computed(() => {
  const allCategories = appState.value.data.map(item => item.category)
  return [...new Set(allCategories)]
})

// 过滤后的文章
const filteredArticles = computed(() => {
  let result = [...appState.value.data]
  
  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    result = result.filter(article => 
      article.title.toLowerCase().includes(query) ||
      article.content.toLowerCase().includes(query)
    )
  }
  
  if (selectedCategory.value) {
    result = result.filter(article => 
      article.category === selectedCategory.value
    )
  }
  
  return result
})

// 分页文章
const paginatedArticles = computed(() => {
  const start = (currentPage.value - 1) * 10
  const end = start + 10
  return filteredArticles.value.slice(start, end)
})

const totalPages = computed(() => {
  return Math.ceil(filteredArticles.value.length / 10)
})

// 格式化日期
const formatDate = (dateString) => {
  return new Date(dateString).toLocaleDateString('zh-CN')
}

// 监听分页变化
watch(currentPage, () => {
  updatePagination(currentPage.value)
})

// 初始化数据
fetchData()
</script>

总结

Vue 3 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的组件逻辑组织方式,还通过组合式函数实现了强大的逻辑复用能力。本文通过详细的代码示例和实际应用案例,展示了如何在真实项目中运用 Composition API 进行组件通信、状态管理和性能优化。

通过合理使用 refreactivecomputed 等响应式API,结合自定义的组合式函数,我们可以构建出更加模块化、可维护的Vue应用。同时,借助组合式函数的复用能力,可以大大减少代码重复,提高开发效率。

在实际项目中,建议遵循以下最佳实践:

  1. 合理组织逻辑:将相关的功能逻辑封装到组合式函数中
  2. 类型安全:充分利用 TypeScript 提供的类型检查
  3. 性能优化:注意避免不必要的重新渲染和计算
  4. 错误处理:建立完善的错误处理机制
  5. 状态管理:根据应用复杂度选择合适的全局状态管理方案

通过深入理解和掌握 Vue 3 Composition API,开发者能够构建出更加现代化、可维护的前端应用,显著提升开发效率和代码质量。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000