Vue 3 Composition API 实战指南:从 Options API 到现代开发模式
Vue 3 的 Composition API 是 Vue 生态系统中最重要的变革之一。它彻底改变了我们组织和复用逻辑的方式,提供了比传统的 Options API 更灵活、更强大的编程模型。本文将深入探讨 Composition API 的核心概念,并通过实际示例展示如何从 Options API 平滑过渡到现代开发模式。
为什么需要 Composition API?
在 Options API 中,我们的代码按照选项(data、methods、computed、watch 等)进行组织。这种方式在小到中型项目中表现良好,但随着项目规模的增长,会出现几个问题:
- 逻辑关注点分散:相关逻辑被拆分到不同的选项中
- 复用困难:mixins 存在命名冲突和来源不清晰的问题
- 类型支持有限:TypeScript 支持不够完善
Composition API 核心概念
1. setup() 函数
setup() 是 Composition API 的入口点,它在组件实例创建之前执行:
<script>
import { ref, computed } from 'vue'
export default {
setup() {
// 响应式数据
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
// 返回模板可用的数据和方法
return {
count,
doubleCount,
increment
}
}
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>
2. 响应式系统:ref 和 reactive
Vue 3 提供了两种创建响应式数据的方式:
import { ref, reactive } from 'vue'
// ref:用于基本类型和对象引用
const count = ref(0)
const user = ref({ name: 'John', age: 25 })
// reactive:用于对象
const state = reactive({
count: 0,
user: { name: 'John', age: 25 }
})
// 访问 ref 的值需要使用 .value
console.log(count.value) // 0
count.value = 1
// reactive 对象可以直接访问
console.log(state.count) // 0
state.count = 1
3. 组合函数(Composables)
这是 Composition API 最强大的特性之一,允许我们创建可复用的逻辑单元:
// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
const triple = computed(() => count.value * 3)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
double,
triple,
increment,
decrement,
reset
}
}
// 在组件中使用
<script>
import { useCounter } from './useCounter'
export default {
setup() {
const counter = useCounter(10)
return {
...counter
}
}
}
</script>
实战示例:用户搜索组件
让我们创建一个实用的用户搜索组件,展示 Composition API 的强大之处:
<script>
import { ref, computed, watch } from 'vue'
import { debounce } from 'lodash-es'
// 组合函数:用户搜索逻辑
export function useUserSearch() {
const searchQuery = ref('')
const users = ref([])
const isLoading = ref(false)
const error = ref(null)
// 计算属性:过滤用户
const filteredUsers = computed(() => {
if (!searchQuery.value) return users.value
const query = searchQuery.value.toLowerCase()
return users.value.filter(user =>
user.name.toLowerCase().includes(query) ||
user.email.toLowerCase().includes(query)
)
})
// 防抖搜索
const debouncedSearch = debounce(async () => {
if (!searchQuery.value.trim()) {
users.value = []
return
}
isLoading.value = true
error.value = null
try {
const response = await fetch(
`https://api.example.com/users?q=${encodeURIComponent(searchQuery.value)}`
)
users.value = await response.json()
} catch (err) {
error.value = '搜索失败,请重试'
console.error('搜索错误:', err)
} finally {
isLoading.value = false
}
}, 300)
// 监听搜索查询变化
watch(searchQuery, debouncedSearch)
return {
searchQuery,
users: filteredUsers,
isLoading,
error
}
}
// 主组件
export default {
setup() {
const { searchQuery, users, isLoading, error } = useUserSearch()
return {
searchQuery,
users,
isLoading,
error
}
}
}
</script>
<template>
<div class="user-search">
<input
v-model="searchQuery"
type="text"
placeholder="搜索用户..."
class="search-input"
/>
<div v-if="isLoading" class="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<ul v-else class="user-list">
<li v-for="user in users" :key="user.id" class="user-item">
<div class="user-info">
<strong>{{ user.name }}</strong>
<small>{{ user.email }}</small>
</div>
</li>
</ul>
</div>
</template>
<style scoped>
.user-search {
max-width: 400px;
margin: 0 auto;
}
.search-input {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.loading, .error {
padding: 10px;
text-align: center;
color: #666;
}
.error {
color: #e74c3c;
}
.user-list {
list-style: none;
padding: 0;
}
.user-item {
padding: 10px;
border-bottom: 1px solid #eee;
}
.user-item:hover {
background-color: #f5f5f5;
}
.user-info {
display: flex;
flex-direction: column;
}
</style>
从 Options API 迁移的最佳实践
- 渐进式迁移:不需要一次性重写所有组件,可以逐步迁移
- 使用组合函数封装逻辑:将相关逻辑提取到组合函数中
- 利用 TypeScript:Composition API 对 TypeScript 有更好的支持
- 保持组件简洁:setup() 函数应该保持简洁,复杂逻辑应该提取到组合函数中
性能优化技巧
import { ref, computed, watchEffect, onMounted, onUnmounted } from 'vue'
// 1. 使用 watchEffect 自动追踪依赖
watchEffect(() => {
console.log('count changed:', count.value)
})
// 2. 组件卸载时清理副作用
onMounted(() => {
const timer = setInterval(() => {
console.log('tick')
}, 1000)
onUnmounted(() => {
clearInterval(timer)
})
})
// 3. 使用 computed 缓存计算结果
const expensiveCalculation = computed(() => {
// 复杂的计算逻辑
return heavyComputation(data.value)
})
总结
Composition API 代表了 Vue 开发的未来方向。它通过提供更灵活的代码组织方式、更好的 TypeScript 支持和更强大的逻辑复用能力,解决了 Options API 在大型项目中的局限性。虽然学习曲线相对较陡,但一旦掌握,你将能够编写出更清晰、更可维护、更高效的 Vue 代码。
建议从新项目开始尝试 Composition API,或者在现有项目中逐步迁移。Vue 官方提供了优秀的迁移指南和工具,确保迁移过程尽可能平滑。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END




暂无评论内容