引言
Vue 3 的发布带来了全新的 Composition API,这是一次革命性的变化,它重新定义了 Vue 组件的编写方式。相比 Vue 2 中的 Options API,Composition API 提供了更灵活、更强大的组件逻辑组织方式,特别是在处理复杂组件逻辑时表现尤为突出。
本文将深入探讨 Vue 3 Composition API 的核心概念和实际应用,从基础概念到高级技巧,帮助开发者掌握这一现代前端开发的重要技能。我们将涵盖组件逻辑复用、响应式数据处理、生命周期管理等关键技能,并提供实用的最佳实践建议。
什么是 Composition API
核心概念
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们使用函数来组织和复用组件逻辑,而不是传统的选项(options)形式。通过 Composition API,我们可以将相关的逻辑代码组织在一起,提高代码的可维护性和可读性。
与 Options API 的对比
在 Vue 2 中,组件逻辑主要通过 options 来组织:
export default {
data() {
return {
count: 0,
message: ''
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
mounted() {
console.log('组件已挂载')
}
}
而在 Vue 3 中,我们可以使用 Composition API:
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('')
const doubledCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('组件已挂载')
})
return {
count,
message,
doubledCount,
increment
}
}
}
响应式数据处理
ref 和 reactive 的基本使用
在 Composition API 中,ref 和 reactive 是两个核心的响应式 API。
import { ref, reactive } from 'vue'
// 使用 ref 创建响应式数据
const count = ref(0)
const name = ref('Vue')
// 使用 reactive 创建响应式对象
const user = reactive({
firstName: 'John',
lastName: 'Doe',
age: 30
})
// 访问和修改值
console.log(count.value) // 0
count.value = 10
console.log(user.firstName) // John
user.firstName = 'Jane'
深层响应式数据处理
对于深层嵌套的对象,我们需要使用 reactive 来确保所有层级都是响应式的:
import { reactive } from 'vue'
const state = reactive({
user: {
profile: {
name: 'John',
settings: {
theme: 'dark'
}
}
}
})
// 修改深层属性
state.user.profile.name = 'Jane'
state.user.profile.settings.theme = 'light'
ref vs reactive 的选择
// 对于基本类型数据,推荐使用 ref
const count = ref(0)
const message = ref('Hello')
// 对于对象或数组,推荐使用 reactive
const userInfo = reactive({
name: 'John',
age: 30,
hobbies: ['reading', 'coding']
})
const userList = reactive([])
响应式数据的解构和展开
import { ref, reactive } from 'vue'
// 注意:直接解构会失去响应性
const state = reactive({
count: 0,
name: 'Vue'
})
// ❌ 错误方式 - 失去响应性
const { count, name } = state
// ✅ 正确方式 - 保持响应性
const countRef = ref(state.count)
const nameRef = ref(state.name)
// 或者使用 toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)
生命周期钩子
基本生命周期管理
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数形式提供:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} 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
}
}
}
组件逻辑复用
Composables 的概念和实践
Composables 是 Vue 3 中推荐的逻辑复用方式,它本质上是可组合的函数。
// 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 doubledCount = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubledCount
}
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const {
count,
increment,
decrement,
reset,
doubledCount
} = useCounter(10)
return {
count,
increment,
decrement,
reset,
doubledCount
}
}
}
复杂的 Composables 实例
// 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)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
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
}
}
// 在组件中使用
import { useFetch } from '@/composables/useFetch'
export default {
setup() {
const url = ref('https://api.example.com/users')
const { data, loading, error, refetch } = useFetch(url)
return {
data,
loading,
error,
refetch
}
}
}
带状态管理的 Composables
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(defaultValue)
// 从 localStorage 初始化值
const storedValue = localStorage.getItem(key)
if (storedValue) {
try {
value.value = JSON.parse(storedValue)
} catch (e) {
console.error('Failed to parse localStorage item:', e)
}
}
// 监听值变化并同步到 localStorage
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}
// 在组件中使用
import { useLocalStorage } from '@/composables/useLocalStorage'
export default {
setup() {
const theme = useLocalStorage('theme', 'light')
const preferences = useLocalStorage('userPreferences', {})
return {
theme,
preferences
}
}
}
计算属性和监听器
computed 的高级用法
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('')
const lastName = ref('')
const age = ref(0)
// 基本计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带 getter 和 setter 的计算属性
const displayName = computed({
get: () => {
if (age.value >= 18) {
return `Mr. ${fullName.value}`
}
return fullName.value
},
set: (value) => {
const names = value.split(' ')
firstName.value = names[0]
lastName.value = names[1] || ''
}
})
// 计算属性的依赖追踪
const isAdult = computed(() => age.value >= 18)
return {
firstName,
lastName,
age,
fullName,
displayName,
isAdult
}
}
}
watch 的使用技巧
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const user = ref({ id: 1, name: 'John' })
// 基本监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
// 监听多个源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
})
// 深度监听对象
watch(user, (newVal, oldVal) => {
console.log('user changed:', newVal)
}, { deep: true })
// 立即执行的监听器
watch(count, (newVal) => {
console.log('count is now:', newVal)
}, { immediate: true })
// watchEffect - 自动追踪依赖
const watchEffectExample = watchEffect(() => {
console.log(`Name is: ${name.value}, Count is: ${count.value}`)
// 会自动追踪 name 和 count 的变化
})
return {
count,
name,
user
}
}
}
事件处理和方法定义
方法的定义和使用
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('')
// 定义方法
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
const updateMessage = (newMessage) => {
message.value = newMessage
}
// 处理表单提交
const handleSubmit = (event) => {
event.preventDefault()
console.log('Form submitted with:', message.value)
// 处理提交逻辑
}
return {
count,
message,
increment,
decrement,
reset,
updateMessage,
handleSubmit
}
}
}
高级事件处理模式
import { ref, watch } from 'vue'
export default {
setup() {
const input = ref('')
const debouncedInput = ref('')
// 防抖处理
const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
const handleInputChange = debounce((value) => {
debouncedInput.value = value
console.log('Debounced input:', value)
}, 300)
// 节流处理
const throttle = (func, limit) => {
let inThrottle
return (...args) => {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
const handleScroll = throttle(() => {
console.log('Scroll event handled')
}, 100)
// 监听输入变化
watch(input, (newVal) => {
handleInputChange(newVal)
})
return {
input,
debouncedInput,
handleScroll
}
}
}
组件通信
父子组件通信
<!-- Parent.vue -->
<template>
<div>
<h2>Parent Component</h2>
<Child
:message="parentMessage"
@child-event="handleChildEvent"
/>
<p>Received from child: {{ childMessage }}</p>
</div>
</template>
<script>
import { ref } from 'vue'
import Child from './Child.vue'
export default {
components: {
Child
},
setup() {
const parentMessage = ref('Hello from parent')
const childMessage = ref('')
const handleChildEvent = (message) => {
childMessage.value = message
console.log('Received from child:', message)
}
return {
parentMessage,
childMessage,
handleChildEvent
}
}
}
</script>
<!-- Child.vue -->
<template>
<div>
<h3>Child Component</h3>
<p>{{ message }}</p>
<button @click="sendToParent">Send to Parent</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
props: ['message'],
emits: ['child-event'],
setup(props, { emit }) {
const sendToParent = () => {
emit('child-event', `Hello from child at ${new Date()}`)
}
return {
sendToParent
}
}
}
</script>
兄弟组件通信
<!-- ComponentA.vue -->
<template>
<div>
<h3>Component A</h3>
<input v-model="sharedData" placeholder="Type something..." />
</div>
</template>
<script>
import { ref, watch } from 'vue'
import { useSharedState } from '@/composables/useSharedState'
export default {
setup() {
const { sharedData } = useSharedState()
return {
sharedData
}
}
}
</script>
// composables/useSharedState.js
import { ref } from 'vue'
const sharedData = ref('')
export function useSharedState() {
return {
sharedData
}
}
性能优化技巧
避免不必要的重渲染
import { ref, computed, shallowRef, markRaw } from 'vue'
export default {
setup() {
const count = ref(0)
// 使用 computed 缓存计算结果
const expensiveComputation = computed(() => {
// 模拟耗时计算
let result = 0
for (let i = 0; i < 1000000; i++) {
result += i
}
return result * count.value
})
// 使用 shallowRef 浅层响应式,避免深度遍历
const shallowData = shallowRef({
nested: { value: 'test' }
})
// 使用 markRaw 避免对象被响应式化
const rawObject = markRaw({
method() {
return 'raw method'
}
})
return {
count,
expensiveComputation,
shallowData,
rawObject
}
}
}
组件懒加载和优化
import { ref, onMounted } from 'vue'
export default {
setup() {
const showComponent = ref(false)
const componentLoaded = ref(false)
const loadComponent = async () => {
if (!componentLoaded.value) {
// 动态导入组件
const { default: LazyComponent } = await import('./LazyComponent.vue')
// 组件加载后设置标志
componentLoaded.value = true
}
showComponent.value = true
}
onMounted(() => {
// 延迟加载组件以优化性能
setTimeout(loadComponent, 1000)
})
return {
showComponent,
loadComponent
}
}
}
最佳实践和常见陷阱
推荐的代码组织方式
// ✅ 好的做法:逻辑分组
export default {
setup() {
// 1. 响应式数据声明
const count = ref(0)
const user = reactive({ name: '', email: '' })
// 2. 计算属性
const isAdult = computed(() => user.age >= 18)
const displayName = computed(() => `${user.firstName} ${user.lastName}`)
// 3. 方法定义
const increment = () => count.value++
const reset = () => {
count.value = 0
user.name = ''
}
// 4. 生命周期钩子
onMounted(() => {
console.log('Component mounted')
})
// 5. 监听器
watch(count, (newVal) => {
console.log('Count changed:', newVal)
})
return {
count,
user,
isAdult,
displayName,
increment,
reset
}
}
}
常见陷阱和解决方案
// ❌ 错误:直接返回 ref 值
export default {
setup() {
const count = ref(0)
// 这样做会丢失响应性
return {
count: count.value // 应该是 count
}
}
}
// ✅ 正确做法
export default {
setup() {
const count = ref(0)
return {
count // 直接返回 ref 对象
}
}
}
// ❌ 错误:在函数内部创建响应式数据
export default {
setup() {
function createData() {
const localCount = ref(0) // 这个 ref 不会保持活跃
return localCount
}
const count = createData()
return { count }
}
}
// ✅ 正确做法
export default {
setup() {
const count = ref(0)
function createData() {
// 在 setup 中定义的 ref 会保持活跃
return count
}
return { count }
}
}
高级主题和未来展望
自定义指令的实现
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
// 创建自定义指令
const focusDirective = {
mounted(el) {
el.focus()
}
}
return {
focusDirective
}
}
}
模块化和工程化实践
// utils/helpers.js
export const formatDate = (date) => {
return new Date(date).toLocaleDateString()
}
export const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// composables/useApi.js
import { ref } from 'vue'
import { debounce } from '@/utils/helpers'
export function useApi() {
const data = ref(null)
const loading = ref(false)
const fetchData = async (url) => {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (error) {
console.error('API Error:', error)
} finally {
loading.value = false
}
}
return {
data,
loading,
fetchData: debounce(fetchData, 300)
}
}
总结
Vue 3 的 Composition API 为前端开发带来了革命性的变化,它提供了更加灵活和强大的组件逻辑组织方式。通过本文的详细介绍,我们涵盖了从基础概念到高级实践的各个方面:
- 响应式数据处理:理解
ref和reactive的使用场景和最佳实践 - 生命周期管理:掌握各种生命周期钩子的正确使用方法
- 逻辑复用:学习如何创建和使用 Composables 来实现代码复用
- 计算属性和监听器:深入理解
computed和watch的高级用法 - 组件通信:掌握父子、兄弟组件间的通信模式
- 性能优化:了解关键的性能优化技巧和最佳实践
通过合理使用 Composition API,我们可以编写出更加模块化、可维护和可复用的 Vue 组件。随着 Vue 生态系统的不断发展,Composition API 将继续演进,为开发者提供更强大的工具来构建现代化的前端应用。
记住,掌握 Composition API 不仅需要理解语法,更重要的是要理解其背后的思维模式。将相关的逻辑组织在一起,避免组件逻辑的碎片化,这是 Composition API 的核心理念。随着实践经验的积累,你会逐渐发现这种开发方式带来的巨大优势。

评论 (0)