Vue3 引入了全新的 Composition API,这是一种组织组件逻辑的新方式,相比于传统的 Options API,它在代码组织、逻辑复用和类型推断方面有着显著优势。本文将带你深入了解 Composition API 的核心概念和实战技巧。
为什么需要 Composition API?
在 Vue2 的 Options API 中,相关逻辑分散在不同的选项中(data、methods、computed、watch 等)。当组件变得复杂时,相关逻辑被拆分到各处,导致代码难以维护和复用。Composition API 的出现解决了这些问题:
- 更好的逻辑组织:相关逻辑可以放在一起,而不是分散在各个选项中
- 更强的逻辑复用:Composables(组合式函数)让逻辑复用变得简单
- 更好的类型推断:与 TypeScript 结合更紧密,类型推断更准确
- 更小的打包体积:更好的 tree-shaking 支持
核心概念:setup() 函数
Composition API 的核心是 setup() 函数,它是组件中使用 Composition API 的入口点。
import { ref, computed, onMounted } from 'vue'
export default {
setup(props, context) {
// 响应式数据
const count = ref(0)
const message = ref('Hello Vue3')
// 计算属性
const doubledCount = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log('组件已挂载')
})
// 暴露给模板
return {
count,
message,
doubledCount,
increment
}
}
}
响应式系统详解
ref 和 reactive
Vue3 提供了两种创建响应式数据的方式:ref 和 reactive。
import { ref, reactive, toRefs } from 'vue'
// ref 用于基本类型
const count = ref(0)
console.log(count.value) // 访问值需要 .value
// reactive 用于对象
const state = reactive({
name: '张三',
age: 25,
skills: ['JavaScript', 'Vue', 'Node.js']
})
// 如果要把 reactive 对象展开为 ref
const { name, age } = toRefs(state)
提示: 在模板中使用时,ref 会自动解包,不需要写
.value。
computed 计算属性
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 只读计算属性
const fullName = computed(() => `${lastName.value}${firstName.value}`)
// 可读可写的计算属性
const reversedName = computed({
get() {
return fullName.value.split('').reverse().join('')
},
set(value) {
const [last, ...rest] = value.split('').reverse()
lastName.value = last
firstName.value = rest.reverse().join('')
}
})
监听器 watch 和 watchEffect
Vue3 提供了两种监听方式,各有适用场景:
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const name = ref('')
// watch - 显式指定要监听的数据
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`)
}, { immediate: true })
// watch 多个数据源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`变化: count=${newCount}, name=${newName}`)
})
// watchEffect - 自动收集依赖
watchEffect(() => {
// 立即执行,并自动追踪函数中使用的所有响应式数据
console.log(`自动追踪: count=${count.value}, name=${name.value}`)
})
生命周期钩子
Composition API 中的生命周期钩子需要从 vue 中显式导入:
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup() {
onMounted(() => {
console.log('组件挂载完成')
})
onBeforeMount(() => {
console.log('即将挂载')
})
onUnmounted(() => {
console.log('组件即将卸载')
})
// 错误捕获
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err, info)
return false // 阻止错误继续传播
})
return {}
}
}
依赖注入:provide 和 inject
对于深层嵌套的组件,Props 层层传递会变得繁琐。使用 provide/inject 可以实现跨多层的组件通信:
// 父组件
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('dark')
const updateTheme = (newTheme) => {
theme.value = newTheme
}
// 提供给所有后代组件
provide('theme', theme)
provide('updateTheme', updateTheme)
}
}
// 子组件(任意层级)
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme')
const updateTheme = inject('updateTheme')
return { theme, updateTheme }
}
}
Composables:组合式函数
Composables 是 Composition API 最强大的特性之一,允许我们将相关逻辑封装成可复用的函数:
// useMouse.js - 封装鼠标位置逻辑
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// useLocalStorage.js - 封装本地存储逻辑
export function useLocalStorage(key, defaultValue) {
const data = ref(JSON.parse(localStorage.getItem(key) ?? JSON.stringify(defaultValue)))
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return data
}
使用 Composable:
import { useMouse, useLocalStorage } from './composables'
export default {
setup() {
const { x, y } = useMouse()
const username = useLocalStorage('username', '')
return { x, y, username }
}
}
实战案例:实现一个可复用的搜索组件
下面我们用 Composition API 实现一个功能完整的搜索组件:
// useSearch.js
import { ref, watch, computed } from 'vue'
export function useSearch(fetchFn, options = {}) {
const { debounce = 300 } = options
const query = ref('')
const results = ref([])
const loading = ref(false)
const error = ref(null)
let debounceTimer = null
const performSearch = async () => {
if (!query.value.trim()) {
results.value = []
return
}
loading.value = true
error.value = null
try {
results.value = await fetchFn(query.value)
} catch (e) {
error.value = e.message
results.value = []
} finally {
loading.value = false
}
}
// 防抖处理
watch(query, () => {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(performSearch, debounce)
})
const hasResults = computed(() => results.value.length > 0)
return {
query,
results,
loading,
error,
hasResults,
performSearch
}
}
在组件中使用:
// SearchComponent.vue
import { useSearch } from './composables/useSearch'
export default {
setup() {
// 模拟搜索 API
const searchAPI = async (keyword) => {
const res = await fetch(`/api/search?q=${keyword}`)
return res.json()
}
const { query, results, loading, error, hasResults } = useSearch(searchAPI, {
debounce: 500
})
return { query, results, loading, error, hasResults }
}
}
与 TypeScript 配合使用
Composition API 与 TypeScript 配合得非常好,以下是类型定义的示例:
import { ref, computed, Ref } from 'vue'
// 定义接口
interface User {
id: number
name: string
email: string
}
// 带类型的 ref
const count = ref<number>(0)
const user = ref<User | null>(null)
// 泛型计算属性
function useFilteredList<T>(list: Ref<T[]>, filter: Ref<(item: T) => boolean>) {
return computed(() => list.value.filter(filter.value))
}
// Composable 的类型定义
interface UseCounterReturn {
count: Ref<number>
increment: () => void
decrement: () => void
}
function useCounter(initial = 0): UseCounterReturn {
const count = ref(initial)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
性能优化技巧
使用 shallowRef 和 shallowReactive
对于大型数据结构,可以使用 shallowRef 和 shallowReactive 避免深层响应式开销:
import { shallowRef, shallowReactive } from 'vue'
// 只追踪顶层变化,内部变化不会触发更新
const state = shallowReactive({
deep: {
nested: {
data: 'initial'
}
}
})
// shallowRef 只追踪 .value 的变化
const data = shallowRef({ large: 'structure' })
组件渲染优化
import { computed, readonly } from 'vue'
// 使用 readonly 防止外部修改
export function useConfig() {
const config = reactive({
apiUrl: 'https://api.example.com',
timeout: 5000
})
// 只暴露只读版本
return readonly(config)
}
// 冻结不需要响应式的大对象
const staticData = Object.freeze([
{ id: 1, label: '选项1' },
{ id: 2, label: '选项2' }
])
总结
Vue3 的 Composition API 为我们提供了一种更灵活、更强大的代码组织方式。通过掌握核心概念(ref、reactive、computed、watch)和最佳实践(Composables、依赖注入),我们可以构建出更易维护、更易测试的 Vue 应用。
建议在实际项目中逐步采用 Composition API,可以从新组件开始,逐步迁移旧组件,同时保持对新特性的学习和探索。















暂无评论内容