Vue 3 Composition API实战:从基础到高级组件状态管理完整教程

CrazyMaster
CrazyMaster 2026-02-09T07:06:09+08:00
0 0 0

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件状态管理和逻辑复用方式。本文将深入探讨 Vue 3 Composition API 的核心概念和实际应用,从基础概念到高级特性,帮助开发者构建更加健壮和可维护的 Vue 3 应用。

什么是 Composition API

核心概念

Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许开发者以函数的形式组织和复用逻辑代码。与传统的 Options API(基于选项的对象结构)不同,Composition API 基于函数的组合方式,使得代码更加灵活和可维护。

在传统的 Vue 2 中,我们使用 datamethodscomputedwatch 等选项来组织组件逻辑。而 Composition API 则将这些功能作为函数暴露出来,开发者可以按照自己的需求自由组合这些函数。

与 Options API 的对比

让我们通过一个简单的例子来看看两种方式的区别:

// Options API (Vue 2)
export default {
  data() {
    return {
      count: 0,
      message: 'Hello'
    }
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    }
  }
}
// Composition API (Vue 3)
import { ref, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const message = ref('Hello')
    
    const reversedMessage = computed(() => {
      return message.value.split('').reverse().join('')
    })
    
    const increment = () => {
      count.value++
    }
    
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    return {
      count,
      message,
      reversedMessage,
      increment
    }
  }
}

响应式数据处理

ref 和 reactive 的基础使用

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

ref 的使用

ref 用于创建基本类型的响应式数据:

import { ref } from 'vue'

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

reactive 的使用

reactive 用于创建对象类型的响应式数据:

import { reactive } from 'vue'

export default {
  setup() {
    // 创建响应式对象
    const state = reactive({
      count: 0,
      name: 'Vue',
      user: {
        firstName: 'John',
        lastName: 'Doe'
      }
    })
    
    // 修改属性
    state.count = 10
    state.user.firstName = 'Jane'
    
    return {
      state
    }
  }
}

响应式数据的深层理解

在使用 refreactive 时,需要特别注意它们的区别:

import { ref, reactive } from 'vue'

export default {
  setup() {
    // ref 创建的是包装对象
    const count = ref(0)
    console.log(count) // RefImpl { value: 0 }
    console.log(count.value) // 0
    
    // reactive 创建的是响应式对象
    const obj = reactive({ count: 0 })
    console.log(obj) // Proxy { count: 0 }
    
    // 在模板中使用时的区别
    return {
      count,  // 需要 .value 访问
      obj     // 直接访问即可
    }
  }
}

组合函数的创建与复用

创建可复用的组合函数

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

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchData = async () => {
    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
    }
  }
  
  watch(url, fetchData, { immediate: true })
  
  return {
    data,
    loading,
    error,
    fetchData
  }
}

使用组合函数

import { defineComponent } from 'vue'
import { useCounter } from '@/composables/useCounter'
import { useFetch } from '@/composables/useFetch'

export default defineComponent({
  name: 'MyComponent',
  setup() {
    // 使用计数器组合函数
    const { count, increment, decrement, doubleCount } = useCounter(0)
    
    // 使用数据获取组合函数
    const { data, loading, error, fetchData } = useFetch('/api/users')
    
    return {
      count,
      increment,
      decrement,
      doubleCount,
      data,
      loading,
      error,
      fetchData
    }
  }
})

组件通信机制

父子组件通信

在 Composition API 中,父子组件通信依然可以通过 props 和 emit 来实现:

// Parent.vue
import { defineComponent } from 'vue'
import Child from './Child.vue'

export default defineComponent({
  name: 'Parent',
  components: {
    Child
  },
  setup() {
    const message = ref('Hello from parent')
    
    const handleChildEvent = (data) => {
      console.log('Received from child:', data)
    }
    
    return {
      message,
      handleChildEvent
    }
  }
})
<!-- Parent.vue -->
<template>
  <div>
    <h1>{{ message }}</h1>
    <Child 
      :message="message" 
      @child-event="handleChildEvent"
    />
  </div>
</template>
// Child.vue
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Child',
  props: {
    message: {
      type: String,
      required: true
    }
  },
  emits: ['child-event'],
  setup(props, { emit }) {
    const localMessage = ref(props.message)
    
    const handleClick = () => {
      emit('child-event', 'Hello from child')
    }
    
    return {
      localMessage,
      handleClick
    }
  }
})

兄弟组件通信

对于兄弟组件间的通信,可以使用事件总线或者状态管理方案:

// composables/useEventBus.js
import { createApp } from 'vue'

const eventBus = createApp({}).config.globalProperties.$bus || {}

export function useEventBus() {
  const emit = (event, data) => {
    eventBus.$emit(event, data)
  }
  
  const on = (event, callback) => {
    eventBus.$on(event, callback)
  }
  
  const off = (event, callback) => {
    eventBus.$off(event, callback)
  }
  
  return {
    emit,
    on,
    off
  }
}

状态管理的高级应用

基于 ref 的简单状态管理

// stores/useGlobalStore.js
import { ref } from 'vue'

const user = ref(null)
const isLoggedIn = ref(false)
const theme = ref('light')

export function useGlobalStore() {
  const setUser = (userData) => {
    user.value = userData
    isLoggedIn.value = !!userData
  }
  
  const setTheme = (newTheme) => {
    theme.value = newTheme
  }
  
  const logout = () => {
    user.value = null
    isLoggedIn.value = false
  }
  
  return {
    user,
    isLoggedIn,
    theme,
    setUser,
    setTheme,
    logout
  }
}

复杂状态管理示例

// stores/useTodoStore.js
import { ref, computed } from 'vue'

export function useTodoStore() {
  const todos = ref([])
  const filter = ref('all')
  
  // 计算属性:过滤后的待办事项
  const filteredTodos = computed(() => {
    switch (filter.value) {
      case 'active':
        return todos.value.filter(todo => !todo.completed)
      case 'completed':
        return todos.value.filter(todo => todo.completed)
      default:
        return todos.value
    }
  })
  
  // 计算属性:已完成的待办事项数量
  const completedCount = computed(() => {
    return todos.value.filter(todo => todo.completed).length
  })
  
  // 添加待办事项
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false
    }
    todos.value.push(newTodo)
  }
  
  // 切换待办事项完成状态
  const toggleTodo = (id) => {
    const todo = todos.value.find(todo => todo.id === id)
    if (todo) {
      todo.completed = !todo.completed
    }
  }
  
  // 删除待办事项
  const removeTodo = (id) => {
    todos.value = todos.value.filter(todo => todo.id !== id)
  }
  
  // 清除已完成的待办事项
  const clearCompleted = () => {
    todos.value = todos.value.filter(todo => !todo.completed)
  }
  
  // 设置过滤器
  const setFilter = (newFilter) => {
    filter.value = newFilter
  }
  
  return {
    todos,
    filter,
    filteredTodos,
    completedCount,
    addTodo,
    toggleTodo,
    removeTodo,
    clearCompleted,
    setFilter
  }
}

生命周期钩子的使用

setup 函数中的生命周期

在 Composition API 中,组件的生命周期钩子需要通过特定的函数来注册:

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

export default {
  setup() {
    // 组件挂载前
    onBeforeMount(() => {
      console.log('Component before mount')
    })
    
    // 组件挂载后
    onMounted(() => {
      console.log('Component mounted')
      // 可以在这里执行 DOM 操作
    })
    
    // 组件更新前
    onBeforeUpdate(() => {
      console.log('Component before update')
    })
    
    // 组件更新后
    onUpdated(() => {
      console.log('Component updated')
    })
    
    // 组件卸载前
    onBeforeUnmount(() => {
      console.log('Component before unmount')
    })
    
    // 组件卸载后
    onUnmounted(() => {
      console.log('Component unmounted')
    })
    
    return {}
  }
}

异步数据加载示例

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const users = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    const fetchUsers = async () => {
      try {
        loading.value = true
        error.value = null
        
        // 模拟 API 调用
        const response = await fetch('/api/users')
        users.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 在组件挂载时获取数据
    onMounted(() => {
      fetchUsers()
    })
    
    return {
      users,
      loading,
      error,
      fetchUsers
    }
  }
}

性能优化技巧

计算属性的优化

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([])
    
    // 使用缓存的计算属性
    const expensiveValue = computed(() => {
      console.log('Computing expensive value...')
      return items.value.reduce((acc, item) => acc + item.value, 0)
    })
    
    // 带有 getter 和 setter 的计算属性
    const fullName = computed({
      get: () => {
        return `${firstName.value} ${lastName.value}`
      },
      set: (value) => {
        const names = value.split(' ')
        firstName.value = names[0]
        lastName.value = names[1] || ''
      }
    })
    
    return {
      items,
      expensiveValue,
      fullName
    }
  }
}

watch 的性能优化

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

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')
    
    // 基本的 watch
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })
    
    // 深度监听
    watch(name, (newVal, oldVal) => {
      console.log(`name changed from ${oldVal} to ${newVal}`)
    }, { deep: true })
    
    // 立即执行
    watch(count, (newVal) => {
      console.log(`Immediate watch: ${newVal}`)
    }, { immediate: true })
    
    // watchEffect - 自动追踪依赖
    watchEffect(() => {
      console.log(`watchEffect: ${count.value} ${name.value}`)
    })
    
    return {
      count,
      name
    }
  }
}

实际项目案例:构建一个完整的任务管理应用

应用结构设计

<!-- App.vue -->
<template>
  <div class="app">
    <header class="app-header">
      <h1>Vue 3 Todo App</h1>
    </header>
    
    <main class="app-main">
      <TodoInput @add-todo="addTodo" />
      
      <TodoFilters 
        :filter="filter"
        :completed-count="completedCount"
        @set-filter="setFilter"
        @clear-completed="clearCompleted"
      />
      
      <TodoList 
        :todos="filteredTodos"
        @toggle-todo="toggleTodo"
        @remove-todo="removeTodo"
      />
    </main>
    
    <footer class="app-footer">
      <p>{{ remainingCount }} items left</p>
    </footer>
  </div>
</template>

<script>
import { defineComponent } from 'vue'
import TodoInput from './components/TodoInput.vue'
import TodoFilters from './components/TodoFilters.vue'
import TodoList from './components/TodoList.vue'
import { useTodoStore } from '@/stores/useTodoStore'

export default defineComponent({
  name: 'App',
  components: {
    TodoInput,
    TodoFilters,
    TodoList
  },
  setup() {
    const { 
      todos, 
      filter, 
      filteredTodos, 
      completedCount, 
      remainingCount,
      addTodo,
      setFilter,
      clearCompleted,
      toggleTodo,
      removeTodo
    } = useTodoStore()
    
    return {
      todos,
      filter,
      filteredTodos,
      completedCount,
      remainingCount,
      addTodo,
      setFilter,
      clearCompleted,
      toggleTodo,
      removeTodo
    }
  }
})
</script>

<style scoped>
.app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.app-header {
  text-align: center;
  margin-bottom: 30px;
}

.app-main {
  margin-bottom: 30px;
}

.app-footer {
  text-align: center;
  color: #666;
}
</style>

组件实现

<!-- components/TodoInput.vue -->
<template>
  <div class="todo-input">
    <input 
      v-model="newTodo"
      @keyup.enter="handleAdd"
      placeholder="What needs to be done?"
      class="todo-input-field"
    />
    <button @click="handleAdd" class="add-button">Add</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  name: 'TodoInput',
  emits: ['add-todo'],
  setup(props, { emit }) {
    const newTodo = ref('')
    
    const handleAdd = () => {
      if (newTodo.value.trim()) {
        emit('add-todo', newTodo.value.trim())
        newTodo.value = ''
      }
    }
    
    return {
      newTodo,
      handleAdd
    }
  }
}
</script>

<style scoped>
.todo-input {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input-field {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.add-button {
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.add-button:hover {
  background-color: #0056b3;
}
</style>
<!-- components/TodoFilters.vue -->
<template>
  <div class="todo-filters">
    <div class="filter-buttons">
      <button 
        v-for="filterOption in filterOptions" 
        :key="filterOption.value"
        :class="{ active: filter === filterOption.value }"
        @click="() => $emit('set-filter', filterOption.value)"
        class="filter-button"
      >
        {{ filterOption.label }}
      </button>
    </div>
    
    <div class="clear-section">
      <button 
        v-if="completedCount > 0" 
        @click="$emit('clear-completed')"
        class="clear-button"
      >
        Clear completed ({{ completedCount }})
      </button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TodoFilters',
  props: {
    filter: {
      type: String,
      required: true
    },
    completedCount: {
      type: Number,
      required: true
    }
  },
  emits: ['set-filter', 'clear-completed'],
  setup() {
    const filterOptions = [
      { value: 'all', label: 'All' },
      { value: 'active', label: 'Active' },
      { value: 'completed', label: 'Completed' }
    ]
    
    return {
      filterOptions
    }
  }
}
</script>

<style scoped>
.todo-filters {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.filter-buttons {
  display: flex;
  gap: 10px;
}

.filter-button {
  padding: 8px 16px;
  background-color: transparent;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
}

.filter-button.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.clear-section {
  text-align: right;
}

.clear-button {
  padding: 8px 16px;
  background-color: transparent;
  color: #dc3545;
  border: 1px solid #dc3545;
  border-radius: 4px;
  cursor: pointer;
}

.clear-button:hover {
  background-color: #dc3545;
  color: white;
}
</style>
<!-- components/TodoList.vue -->
<template>
  <div class="todo-list">
    <ul v-if="todos.length > 0" class="todo-items">
      <li 
        v-for="todo in todos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
        class="todo-item"
      >
        <input 
          type="checkbox"
          :checked="todo.completed"
          @change="() => $emit('toggle-todo', todo.id)"
          class="todo-checkbox"
        />
        <span class="todo-text">{{ todo.text }}</span>
        <button 
          @click="() => $emit('remove-todo', todo.id)"
          class="delete-button"
        >
          ×
        </button>
      </li>
    </ul>
    
    <p v-else class="empty-message">
      No todos yet. Add one above!
    </p>
  </div>
</template>

<script>
export default {
  name: 'TodoList',
  props: {
    todos: {
      type: Array,
      required: true
    }
  },
  emits: ['toggle-todo', 'remove-todo']
}
</script>

<style scoped>
.todo-list {
  margin-bottom: 20px;
}

.todo-items {
  list-style: none;
  padding: 0;
  margin: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #999;
}

.todo-checkbox {
  margin-right: 10px;
}

.todo-text {
  flex: 1;
  word-break: break-word;
}

.delete-button {
  background-color: #dc3545;
  color: white;
  border: none;
  border-radius: 50%;
  width: 24px;
  height: 24px;
  cursor: pointer;
  font-size: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.empty-message {
  text-align: center;
  color: #999;
  padding: 20px;
}
</style>

最佳实践总结

代码组织规范

  1. 合理使用组合函数:将可复用的逻辑封装到组合函数中
  2. 组件结构清晰:保持组件的单一职责原则
  3. 命名规范统一:使用一致的命名约定
  4. 文档注释完整:为复杂的逻辑添加适当的注释

性能优化建议

  1. 避免不必要的计算:合理使用 computedwatch
  2. 及时清理监听器:在组件卸载时清理相关资源
  3. 批量更新数据:减少 DOM 操作的频率
  4. 合理使用缓存:利用 Vue 的响应式系统特性

开发工具推荐

  1. Vue DevTools:调试和分析 Vue 应用
  2. Volar 插件:VS Code 中的 Vue 3 支持
  3. ESLint 配置:代码质量检查
  4. TypeScript 支持:提供更好的类型安全

结语

Vue 3 的 Composition API 为前端开发带来了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们从基础概念到高级应用,全面了解了 Composition API 的各种特性和使用方法。

掌握 Composition API 不仅能帮助我们编写更清晰、更可维护的代码,还能让我们更好地组织复杂的业务逻辑。在实际项目中,我们应该根据具体需求选择合适的开发模式,并充分利用 Composition API 提供的各种功能来构建高质量的 Vue 3 应用。

随着 Vue 生态系统的不断发展,Composition API 将继续为我们提供更多的可能性。建议开发者深入学习和实践,不断提升自己的 Vue 开发技能,为用户创造更好的产品体验。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000