引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。作为 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 的优势
- 更好的逻辑复用:通过组合函数,可以轻松地在多个组件之间共享逻辑
- 更强的类型支持:与 TypeScript 集成更好,提供更准确的类型推断
- 更灵活的代码组织:按照功能而不是选项类型来组织代码
- 更好的性能:减少了不必要的副作用和计算
- 更容易测试:逻辑可以独立于组件进行测试
响应式 API 详解
reactive 和 ref 的基本使用
在 Composition API 中,响应式数据的创建主要通过 reactive 和 ref 两个核心函数来实现。
import { reactive, ref } from 'vue'
// 使用 ref 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue 3')
// 使用 reactive 创建响应式对象
const state = reactive({
name: 'John',
age: 25,
hobbies: ['reading', 'coding']
})
// 访问和修改数据
console.log(count.value) // 0
count.value = 10
console.log(state.name) // John
state.name = 'Jane'
reactive vs ref 的选择
选择使用 ref 还是 reactive 主要取决于数据的类型:
// 基本类型数据使用 ref
const count = ref(0)
const name = ref('Vue')
// 对象类型数据使用 reactive
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing',
country: 'China'
}
})
// 但是需要注意,如果对象中的属性是基本类型,需要手动包装
const state = reactive({
count: ref(0), // 这样可以保持响应性
name: ref('Vue')
})
computed 计算属性
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('')
}
})
watch 监听器
watch 函数用于监听响应式数据的变化:
import { ref, watch } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 监听单个响应式变量
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 监听多个响应式变量
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log(`count: ${oldCount} -> ${newCount}, message: ${oldMessage} -> ${newMessage}`)
})
// 深度监听对象
const user = reactive({
name: 'John',
profile: {
age: 25
}
})
watch(
() => user.profile,
(newProfile, oldProfile) => {
console.log('profile changed')
},
{ deep: true }
)
生命周期钩子
组件生命周期的使用
在 Composition API 中,生命周期钩子通过对应的函数来调用:
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
export default {
setup() {
// 组件挂载前
onBeforeMount(() => {
console.log('组件即将挂载')
})
// 组件挂载后
onMounted(() => {
console.log('组件已挂载')
// 可以在这里进行 DOM 操作
})
// 组件更新前
onBeforeUpdate(() => {
console.log('组件即将更新')
})
// 组件更新后
onUpdated(() => {
console.log('组件已更新')
})
// 组件卸载前
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
// 组件卸载后
onUnmounted(() => {
console.log('组件已卸载')
})
return {
// 返回的数据和方法
}
}
}
实际应用示例
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const timer = ref(null)
const count = ref(0)
// 启动定时器
onMounted(() => {
timer.value = setInterval(() => {
count.value++
}, 1000)
})
// 清理定时器
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
return {
count
}
}
}
组件通信
父子组件通信
在 Composition API 中,父子组件通信依然遵循 Vue 的标准模式:
// 父组件
import { ref } from 'vue'
export default {
setup() {
const parentMessage = ref('Hello from parent')
const handleChildEvent = (message) => {
console.log('Received from child:', message)
}
return {
parentMessage,
handleChildEvent
}
}
}
<!-- 父组件模板 -->
<template>
<div>
<child-component
:message="parentMessage"
@child-event="handleChildEvent"
/>
</div>
</template>
// 子组件
export default {
props: ['message'],
emits: ['child-event'],
setup(props, { emit }) {
const childMessage = ref('Hello from child')
const sendMessageToParent = () => {
emit('child-event', childMessage.value)
}
return {
sendMessageToParent
}
}
}
provide 和 inject
provide 和 inject 是 Composition API 中用于跨层级组件通信的重要工具:
// 祖先组件
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')
// 使用注入的数据
console.log(theme.value) // dark
console.log(user.value.name) // John
return {
theme,
user
}
}
}
组合函数(Composables)开发
组合函数的基本概念
组合函数是 Vue 3 Composition API 中的核心概念之一,它是一个封装了可复用逻辑的函数。组合函数以 use 开头命名,遵循统一的命名规范。
// 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
}
}
使用组合函数
import { useCounter } from './composables/useCounter'
export default {
setup() {
const {
count,
increment,
decrement,
reset,
doubleCount
} = useCounter(10)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
}
高级组合函数示例
// useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(defaultValue)
// 从 localStorage 初始化值
try {
const stored = localStorage.getItem(key)
if (stored) {
value.value = JSON.parse(stored)
}
} catch (error) {
console.error('Failed to initialize from localStorage:', error)
}
// 监听值变化并保存到 localStorage
watch(value, (newValue) => {
try {
localStorage.setItem(key, JSON.stringify(newValue))
} catch (error) {
console.error('Failed to save to localStorage:', error)
}
}, { deep: true })
return value
}
从 Options API 迁移到 Composition API
基本迁移策略
将现有组件从 Options API 迁移到 Composition API 需要遵循一定的步骤:
// Options API 组件
export default {
name: 'UserCard',
props: ['user'],
data() {
return {
loading: false,
error: null
}
},
computed: {
displayName() {
return `${this.user.firstName} ${this.user.lastName}`
},
isAdult() {
return this.user.age >= 18
}
},
methods: {
async fetchUserData() {
this.loading = true
try {
const response = await fetch(`/api/users/${this.user.id}`)
this.user = await response.json()
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
reset() {
this.$emit('reset')
}
},
mounted() {
this.fetchUserData()
}
}
// Composition API 组件
import { ref, computed, onMounted } from 'vue'
export default {
name: 'UserCard',
props: ['user'],
setup(props, { emit }) {
const loading = ref(false)
const error = ref(null)
const displayName = computed(() => {
return `${props.user.firstName} ${props.user.lastName}`
})
const isAdult = computed(() => {
return props.user.age >= 18
})
const fetchUserData = async () => {
loading.value = true
try {
const response = await fetch(`/api/users/${props.user.id}`)
// 注意:这里需要重新赋值给 props 或者使用 reactive
// 实际应用中可能需要其他处理方式
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const reset = () => {
emit('reset')
}
onMounted(() => {
fetchUserData()
})
return {
loading,
error,
displayName,
isAdult,
reset
}
}
}
复杂组件的迁移示例
// 原始 Options API 组件
export default {
name: 'ProductList',
data() {
return {
products: [],
loading: false,
error: null,
searchQuery: '',
currentPage: 1,
itemsPerPage: 10
}
},
computed: {
filteredProducts() {
return this.products.filter(product =>
product.name.toLowerCase().includes(this.searchQuery.toLowerCase())
)
},
paginatedProducts() {
const start = (this.currentPage - 1) * this.itemsPerPage
return this.filteredProducts.slice(start, start + this.itemsPerPage)
},
totalPages() {
return Math.ceil(this.filteredProducts.length / this.itemsPerPage)
}
},
methods: {
async fetchProducts() {
this.loading = true
try {
const response = await fetch('/api/products')
this.products = await response.json()
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
handlePageChange(page) {
this.currentPage = page
},
handleSearch(query) {
this.searchQuery = query
this.currentPage = 1
}
},
mounted() {
this.fetchProducts()
}
}
// Composition API 组件
import { ref, computed, onMounted } from 'vue'
export default {
name: 'ProductList',
setup() {
const products = ref([])
const loading = ref(false)
const error = ref(null)
const searchQuery = ref('')
const currentPage = ref(1)
const itemsPerPage = ref(10)
const filteredProducts = computed(() => {
return products.value.filter(product =>
product.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const paginatedProducts = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage.value
return filteredProducts.value.slice(start, start + itemsPerPage.value)
})
const totalPages = computed(() => {
return Math.ceil(filteredProducts.value.length / itemsPerPage.value)
})
const fetchProducts = async () => {
loading.value = true
try {
const response = await fetch('/api/products')
products.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const handlePageChange = (page) => {
currentPage.value = page
}
const handleSearch = (query) => {
searchQuery.value = query
currentPage.value = 1
}
onMounted(() => {
fetchProducts()
})
return {
products,
loading,
error,
searchQuery,
currentPage,
itemsPerPage,
filteredProducts,
paginatedProducts,
totalPages,
handlePageChange,
handleSearch
}
}
}
性能优化技巧
合理使用响应式 API
// 不好的做法:过度使用 reactive
const badExample = reactive({
name: '',
age: 0,
email: '',
phone: '',
address: {
street: '',
city: '',
country: ''
},
hobbies: [],
skills: []
})
// 好的做法:按需使用 ref 和 reactive
const name = ref('')
const age = ref(0)
const email = ref('')
const phone = ref('')
const address = reactive({
street: '',
city: '',
country: ''
})
const hobbies = ref([])
const skills = ref([])
避免不必要的计算
// 不好的做法:在每次渲染时都进行复杂计算
export default {
setup() {
const items = ref([])
// 这种方式会在每次渲染时重新计算
const expensiveCalculation = computed(() => {
return items.value.reduce((acc, item) => {
// 复杂的计算逻辑
return acc + item.value * 2
}, 0)
})
return {
expensiveCalculation
}
}
}
// 好的做法:使用 watch 和缓存机制
export default {
setup() {
const items = ref([])
const cachedResult = ref(null)
watch(items, () => {
// 只在 items 变化时重新计算
cachedResult.value = items.value.reduce((acc, item) => {
return acc + item.value * 2
}, 0)
}, { immediate: true })
return {
cachedResult
}
}
}
组件级别的优化
import { shallowRef, markRaw } from 'vue'
export default {
setup() {
// 使用 shallowRef 只响应顶层属性变化
const shallowData = shallowRef({
name: 'John',
profile: {
age: 25
}
})
// 使用 markRaw 避免对象被转为响应式
const rawObject = markRaw({
method: function() {
return 'raw method'
}
})
return {
shallowData,
rawObject
}
}
}
最佳实践和注意事项
组合函数命名规范
// 好的命名方式
export function useApi() { /* ... */ }
export function useAuth() { /* ... */ }
export function useStorage() { /* ... */ }
export function useValidation() { /* ... */ }
// 不好的命名方式
export function api() { /* ... */ }
export function auth() { /* ... */ }
export function storage() { /* ... */ }
错误处理和边界情况
import { ref, watch } from 'vue'
export function useAsyncData(fetcher) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const execute = async (...args) => {
loading.value = true
error.value = null
try {
data.value = await fetcher(...args)
} catch (err) {
error.value = err
console.error('Async data fetching failed:', err)
} finally {
loading.value = false
}
}
// 立即执行
if (typeof fetcher === 'function') {
execute()
}
return {
data,
loading,
error,
execute
}
}
TypeScript 支持
import { ref, computed, Ref } from 'vue'
interface User {
id: number
name: string
email: string
}
export function useUser(): {
user: Ref<User | null>
loading: Ref<boolean>
error: Ref<string | null>
fetchUser: (id: number) => Promise<void>
} {
const user = ref<User | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const fetchUser = async (id: number) => {
loading.value = true
error.value = null
try {
const response = await fetch(`/api/users/${id}`)
user.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
user,
loading,
error,
fetchUser
}
}
实际项目案例
完整的购物车组件示例
<template>
<div class="cart">
<h2>购物车</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<div
v-for="item in cartItems"
:key="item.id"
class="cart-item"
>
<span>{{ item.name }}</span>
<span>数量: {{ item.quantity }}</span>
<span>小计: ¥{{ item.price * item.quantity }}</span>
<button @click="removeItem(item.id)">删除</button>
</div>
<div class="cart-summary">
<p>总数量: {{ totalItems }}</p>
<p>总价: ¥{{ totalPrice }}</p>
<button @click="checkout">结算</button>
</div>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { useLocalStorage } from './composables/useLocalStorage'
export default {
name: 'ShoppingCart',
setup() {
const cartItems = useLocalStorage('cartItems', [])
const loading = ref(false)
const error = ref(null)
// 计算属性
const totalItems = computed(() => {
return cartItems.value.reduce((total, item) => total + item.quantity, 0)
})
const totalPrice = computed(() => {
return cartItems.value.reduce((total, item) =>
total + (item.price * item.quantity), 0
)
})
// 方法
const removeItem = (id) => {
cartItems.value = cartItems.value.filter(item => item.id !== id)
}
const checkout = async () => {
loading.value = true
error.value = null
try {
await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({
items: cartItems.value,
total: totalPrice.value
})
})
// 清空购物车
cartItems.value = []
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
cartItems,
loading,
error,
totalItems,
totalPrice,
removeItem,
checkout
}
}
}
</script>
<style scoped>
.cart {
padding: 20px;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.cart-summary {
margin-top: 20px;
padding-top: 20px;
border-top: 2px solid #eee;
}
</style>
总结
Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅解决了传统 Options API 在大型项目中的局限性,还提供了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们了解了:
- 响应式 API 的核心概念和使用方法,包括
ref、reactive、computed和watch - 生命周期钩子 的正确使用方式
- 组件通信 的多种实现方案
- 组合函数 的开发模式和最佳实践
- 从 Options API 迁移 的具体步骤和注意事项
- 性能优化 的关键技巧
掌握 Composition API 不仅能够提升代码质量,还能显著改善团队协作效率。在实际项目中,建议根据具体需求选择合适的 API 使用方式,并遵循一致的命名规范和编码风格。
随着 Vue 3 生态系统的不断完善,Composition API 将成为现代 Vue 开发的标准模式。持续学习和实践是掌握这一技术的关键,希望本文能够为你的 Vue 3 开发之旅提供有价值的指导和帮助。

评论 (0)