引言
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 中,我们使用 data、methods、computed、watch 等选项来组织组件逻辑。而 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 提供了两种主要的响应式数据处理方式:ref 和 reactive。
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
}
}
}
响应式数据的深层理解
在使用 ref 和 reactive 时,需要特别注意它们的区别:
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>
最佳实践总结
代码组织规范
- 合理使用组合函数:将可复用的逻辑封装到组合函数中
- 组件结构清晰:保持组件的单一职责原则
- 命名规范统一:使用一致的命名约定
- 文档注释完整:为复杂的逻辑添加适当的注释
性能优化建议
- 避免不必要的计算:合理使用
computed和watch - 及时清理监听器:在组件卸载时清理相关资源
- 批量更新数据:减少 DOM 操作的频率
- 合理使用缓存:利用 Vue 的响应式系统特性
开发工具推荐
- Vue DevTools:调试和分析 Vue 应用
- Volar 插件:VS Code 中的 Vue 3 支持
- ESLint 配置:代码质量检查
- TypeScript 支持:提供更好的类型安全
结语
Vue 3 的 Composition API 为前端开发带来了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们从基础概念到高级应用,全面了解了 Composition API 的各种特性和使用方法。
掌握 Composition API 不仅能帮助我们编写更清晰、更可维护的代码,还能让我们更好地组织复杂的业务逻辑。在实际项目中,我们应该根据具体需求选择合适的开发模式,并充分利用 Composition API 提供的各种功能来构建高质量的 Vue 3 应用。
随着 Vue 生态系统的不断发展,Composition API 将继续为我们提供更多的可能性。建议开发者深入学习和实践,不断提升自己的 Vue 开发技能,为用户创造更好的产品体验。

评论 (0)