前言
Vue 3的发布带来了全新的开发体验,其中最引人注目的特性之一就是Composition API。相比于Vue 2的Options API,Composition API提供了一种更加灵活、强大的组件状态管理方式。本文将深入探讨Composition API的核心概念、实际应用场景以及最佳实践,帮助开发者从基础语法逐步掌握这一现代Vue开发模式。
什么是Composition API
核心概念
Composition API是Vue 3中引入的一种新的组件状态管理方式,它将组件的逻辑组织方式从传统的Options API(选项式API)转变为基于函数的组合式API。这种设计模式使得开发者可以更灵活地组织和复用组件逻辑。
在传统的Options API中,组件的逻辑被分散在不同的选项中(data、methods、computed、watch等),而Composition API允许我们将相关的逻辑组织在一起,形成更清晰的代码结构。
与Options API的区别
// Vue 2 Options API
export default {
data() {
return {
count: 0,
name: 'Vue'
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
// Vue 3 Composition API
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const doubledCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
return {
count,
name,
doubledCount,
increment
}
}
}
响应式数据管理
基础响应式API
Composition API提供了多种响应式数据管理的API,其中最核心的是ref和reactive。
Ref API
ref用于创建响应式的数据引用,它可以处理基本数据类型和对象类型:
import { ref } from 'vue'
// 基本数据类型
const count = ref(0)
const message = ref('Hello Vue')
// 对象类型
const user = ref({
name: 'John',
age: 30
})
// 访问和修改
console.log(count.value) // 0
count.value = 10
console.log(count.value) // 10
// 对象属性访问
console.log(user.value.name) // John
user.value.name = 'Jane'
Reactive API
reactive用于创建响应式对象,它会将对象的所有属性都转换为响应式:
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'John',
age: 30
}
})
// 修改属性
state.count = 5
state.user.name = 'Jane'
// 直接赋值对象会丢失响应性
// state = { count: 10 } // 这样会丢失响应性
深度响应式与浅响应式
import { reactive, shallowReactive, toRaw } from 'vue'
// 深度响应式
const deepState = reactive({
nested: {
deep: {
value: 1
}
}
})
// 修改深层属性会触发响应
deepState.nested.deep.value = 2
// 浅响应式
const shallowState = shallowReactive({
nested: {
deep: {
value: 1
}
}
})
// 只有顶层属性是响应式的
shallowState.nested = { deep: { value: 2 } } // 会触发响应
shallowState.nested.deep.value = 2 // 不会触发响应
响应式数组和Map
import { ref, reactive } from 'vue'
// 数组响应式
const list = ref([1, 2, 3])
list.value.push(4) // 会触发响应
list.value[0] = 10 // 会触发响应
// 对象响应式
const map = reactive(new Map())
map.set('key', 'value') // 会触发响应
组合函数(Composables)的使用
创建可复用的逻辑
组合函数是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 doubled = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubled
}
}
// 使用组合函数
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const { count, increment, decrement, doubled } = useCounter(10)
return {
count,
increment,
decrement,
doubled
}
}
}
复杂组合函数示例
// 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 () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 自动获取数据
fetchData()
// 监听url变化
watch(url, fetchData)
return {
data,
loading,
error,
refetch: fetchData
}
}
// 使用示例
export default {
setup() {
const { data, loading, error, refetch } = useFetch('/api/users')
return {
data,
loading,
error,
refetch
}
}
}
组件通信模式
父子组件通信
Props传递
// 父组件
import { ref } from 'vue'
export default {
setup() {
const message = ref('Hello from parent')
const count = ref(0)
return {
message,
count
}
}
}
// 子组件
export default {
props: {
message: String,
count: Number
},
setup(props) {
// props是响应式的
console.log(props.message) // 'Hello from parent'
return {}
}
}
emit事件
// 子组件
export default {
emits: ['update-count', 'submit'],
setup(props, { emit }) {
const handleIncrement = () => {
emit('update-count', props.count + 1)
}
const handleSubmit = (data) => {
emit('submit', data)
}
return {
handleIncrement,
handleSubmit
}
}
}
// 父组件
export default {
setup() {
const count = ref(0)
const handleUpdateCount = (newCount) => {
count.value = newCount
}
return {
count,
handleUpdateCount
}
}
}
兄弟组件通信
// composables/useEventBus.js
import { reactive } from 'vue'
export function useEventBus() {
const events = reactive({})
const on = (event, callback) => {
if (!events[event]) {
events[event] = []
}
events[event].push(callback)
}
const emit = (event, data) => {
if (events[event]) {
events[event].forEach(callback => callback(data))
}
}
const off = (event, callback) => {
if (events[event]) {
events[event] = events[event].filter(cb => cb !== callback)
}
}
return {
on,
emit,
off
}
}
// 使用示例
// ComponentA.vue
import { useEventBus } from '@/composables/useEventBus'
export default {
setup() {
const { emit } = useEventBus()
const sendMessage = () => {
emit('message', 'Hello from Component A')
}
return {
sendMessage
}
}
}
// ComponentB.vue
import { useEventBus } from '@/composables/useEventBus'
export default {
setup() {
const { on } = useEventBus()
const message = ref('')
on('message', (data) => {
message.value = data
})
return {
message
}
}
}
高级响应式特性
计算属性和监听器
import { ref, computed, watch, watchEffect } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
const age = ref(30)
// 计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
const isAdult = computed(() => {
return age.value >= 18
})
// 监听器
const watchCount = ref(0)
// 基本监听
watch(watchCount, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
})
// 监听多个源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Name changed from ${oldFirst} ${oldLast} to ${newFirst} ${newLast}`)
})
// 深度监听
const nestedData = ref({
user: {
profile: {
name: 'John'
}
}
})
watch(nestedData, (newVal, oldVal) => {
console.log('Nested data changed')
}, { deep: true })
// watchEffect
const effectCount = ref(0)
watchEffect(() => {
console.log(`Effect: ${effectCount.value}`)
// 这个effect会自动追踪所有响应式依赖
})
return {
firstName,
lastName,
age,
fullName,
isAdult,
watchCount,
effectCount
}
}
}
异步操作和生命周期
import { ref, onMounted, onUnmounted, onUpdated } from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
// 异步操作
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/data')
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 生命周期钩子
onMounted(() => {
console.log('Component mounted')
fetchData()
})
onUnmounted(() => {
console.log('Component unmounted')
})
onUpdated(() => {
console.log('Component updated')
})
// 清理定时器
const timer = ref(null)
onMounted(() => {
timer.value = setInterval(() => {
console.log('Timer tick')
}, 1000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
return {
data,
loading,
error,
fetchData
}
}
}
复杂组件状态管理
状态管理模式
// stores/useGlobalStore.js
import { reactive, readonly } from 'vue'
export function useGlobalStore() {
const state = reactive({
user: null,
theme: 'light',
notifications: []
})
const setUser = (user) => {
state.user = user
}
const setTheme = (theme) => {
state.theme = theme
}
const addNotification = (notification) => {
state.notifications.push({
id: Date.now(),
...notification,
timestamp: new Date()
})
}
const removeNotification = (id) => {
state.notifications = state.notifications.filter(n => n.id !== id)
}
const clearNotifications = () => {
state.notifications = []
}
return {
state: readonly(state),
setUser,
setTheme,
addNotification,
removeNotification,
clearNotifications
}
}
// 在组件中使用
export default {
setup() {
const { state, setUser, setTheme } = useGlobalStore()
const handleLogin = async (credentials) => {
try {
const user = await login(credentials)
setUser(user)
} catch (error) {
console.error('Login failed:', error)
}
}
return {
state,
handleLogin,
setTheme
}
}
}
表单处理
// composables/useForm.js
import { ref, reactive } from 'vue'
export function useForm(initialData = {}) {
const formData = reactive({ ...initialData })
const errors = ref({})
const isSubmitting = ref(false)
const validate = (rules) => {
const newErrors = {}
Object.keys(rules).forEach(field => {
const rule = rules[field]
const value = formData[field]
if (rule.required && !value) {
newErrors[field] = `${field} is required`
}
if (rule.minLength && value.length < rule.minLength) {
newErrors[field] = `${field} must be at least ${rule.minLength} characters`
}
if (rule.email && !/\S+@\S+\.\S+/.test(value)) {
newErrors[field] = 'Invalid email format'
}
})
errors.value = newErrors
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (submitFn) => {
if (!validate()) return
isSubmitting.value = true
try {
const result = await submitFn(formData)
return result
} catch (error) {
console.error('Form submission error:', error)
throw error
} finally {
isSubmitting.value = false
}
}
const reset = () => {
Object.keys(formData).forEach(key => {
formData[key] = initialData[key] || ''
})
errors.value = {}
}
const setField = (field, value) => {
formData[field] = value
}
return {
formData,
errors,
isSubmitting,
validate,
handleSubmit,
reset,
setField
}
}
// 使用示例
export default {
setup() {
const {
formData,
errors,
isSubmitting,
validate,
handleSubmit,
reset
} = useForm({
name: '',
email: '',
message: ''
})
const rules = {
name: { required: true, minLength: 2 },
email: { required: true, email: true },
message: { required: true, minLength: 10 }
}
const handleFormSubmit = async (data) => {
// 提交表单逻辑
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
return response.json()
}
const onSubmit = async () => {
await handleSubmit(handleFormSubmit)
}
return {
formData,
errors,
isSubmitting,
onSubmit,
reset
}
}
}
最佳实践和性能优化
组合函数设计原则
// 好的设计模式
// composables/useApi.js
import { ref, watch } from 'vue'
export function useApi(url, options = {}) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const retryCount = ref(0)
const fetch = async () => {
if (loading.value) return
loading.value = true
error.value = null
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
retryCount.value = 0
} catch (err) {
error.value = err
retryCount.value++
} finally {
loading.value = false
}
}
const retry = () => {
fetch()
}
// 自动刷新
if (options.autoFetch !== false) {
fetch()
}
// 监听url变化
watch(url, fetch)
return {
data,
loading,
error,
retry,
retryCount,
fetch
}
}
性能优化技巧
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const list = ref([])
const searchTerm = ref('')
const debouncedSearch = ref('')
// 防抖搜索
const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
const debouncedSearchHandler = debounce((value) => {
debouncedSearch.value = value
}, 300)
// 计算属性优化
const filteredList = computed(() => {
if (!debouncedSearch.value) return list.value
return list.value.filter(item =>
item.name.toLowerCase().includes(debouncedSearch.value.toLowerCase())
)
})
// 监听优化
const handleSearch = (value) => {
searchTerm.value = value
debouncedSearchHandler(value)
}
// 资源清理
let timer = null
onMounted(() => {
timer = setInterval(() => {
// 定期执行的逻辑
}, 1000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
return {
list,
searchTerm,
filteredList,
handleSearch
}
}
}
总结
Vue 3的Composition API为现代前端开发提供了强大的工具集。通过本文的详细介绍,我们看到了从基础语法到复杂应用的完整学习路径。Composition API的优势在于:
- 更好的逻辑组织:将相关的逻辑组合在一起,提高代码可读性和维护性
- 更强的复用能力:通过组合函数实现逻辑复用
- 更灵活的开发模式:相比Options API更加灵活
- 更好的TypeScript支持:提供更好的类型推断能力
在实际开发中,建议根据项目需求选择合适的API模式,对于简单组件可以使用Options API,对于复杂组件则推荐使用Composition API。同时,要善于利用组合函数来封装可复用的逻辑,这样可以让代码更加模块化和易于维护。
随着Vue生态的不断发展,Composition API将成为现代Vue开发的标准模式,掌握它对于前端开发者来说是必不可少的技能。通过本文的学习,相信读者已经对Vue 3 Composition API有了深入的理解,并能够在实际项目中灵活运用。

评论 (0)