Vue 3 Composition API 详解
Vue 3 Composition API 详解
Composition API 是 Vue 3 引入的一组基于函数的 API,它提供了比 Options API 更灵活的代码组织方式,专门用来解决大型组件"逻辑碎片化"的问题。
如果把 Options API 比作"按抽屉分类"的收纳方式——所有的 data 放一个抽屉、所有的 methods 放一个抽屉、所有的 watch 放一个抽屉;那么 Composition API 就是"按功能打包"的收纳方式——和"搜索功能"相关的响应式数据、计算属性、监听器、方法全部聚在一起。当一个组件长到几百行时,后者的可维护性优势会非常明显。
一、为什么需要 Composition API
在 Vue 2 的 Options API 中,一段完整的业务逻辑往往被强制拆散到不同的选项里:
// Options API:一个"用户搜索"功能被拆到 4 个地方
export default {
data() {
return {
searchKeyword: '', // 搜索相关
searchResult: [], // 搜索相关
pageSize: 10, // 分页相关
currentPage: 1 // 分页相关
}
},
computed: {
filteredResult() { // 搜索相关
return this.searchResult.filter(item => item.active)
},
totalPages() { // 分页相关
return Math.ceil(this.searchResult.length / this.pageSize)
}
},
watch: {
searchKeyword() { // 搜索相关
this.doSearch()
}
},
methods: {
doSearch() { /* 搜索相关 */ },
changePage() { /* 分页相关 */ }
}
}可以看到,"搜索"和"分页"两块逻辑在每个选项里都交织在一起。组件越大,来回滚动定位代码的成本越高。Composition API 允许把同一功能的所有代码写在一起,甚至抽成独立函数复用:
import { ref, computed, watch } from 'vue'
export default {
setup() {
// ===== 搜索逻辑聚在一起 =====
const searchKeyword = ref('')
const searchResult = ref([])
const filteredResult = computed(() =>
searchResult.value.filter(item => item.active)
)
const doSearch = () => { /* ... */ }
watch(searchKeyword, doSearch)
// ===== 分页逻辑聚在一起 =====
const pageSize = ref(10)
const currentPage = ref(1)
const totalPages = computed(() =>
Math.ceil(searchResult.value.length / pageSize.value)
)
const changePage = (p) => { currentPage.value = p }
return {
searchKeyword, filteredResult,
currentPage, totalPages, changePage
}
}
}二、setup() 函数
- 在 ```beforeCreate``` 之前执行,此时组件实例尚未创建,所以 **不能使用 ```this```**。
- 接收两个参数:```props```(响应式的,不能解构,否则丢失响应性)和 ```context```(普通对象,可解构)。
- 返回的对象会暴露给模板、计算属性、生命周期等。
import { ref, toRefs } from 'vue'
export default {
props: {
title: String,
count: Number
},
emits: ['update', 'close'],
setup(props, context) {
// context 包含 attrs / slots / emit / expose,可安全解构
const { emit, attrs, slots, expose } = context
const localCount = ref(props.count)
// 正确:直接访问 props.title 保持响应式
console.log(props.title)
// 错误示范:const { title } = props 会丢失响应性
// 若确实需要解构,使用 toRefs 保持响应式
const { title } = toRefs(props)
const handleClick = () => {
emit('update', localCount.value + 1)
}
return { localCount, title, handleClick }
}
}
### 三、`<script setup>` 语法糖
<script setup>
import { ref, computed } from 'vue'
// 顶层变量/函数自动暴露给模板,无需 return
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
</script>
<template>
<button @click="increment">count is {{ count }}, double is {{ double }}</button>
</template>对比 ```setup()```,同样的逻辑在 ```
### 十、真实案例:可复用的分页表格逻辑
下面把前面所有知识点串起来,实现一个真实项目中常见的"带搜索、防抖、分页、加载态"的列表页 composable。
import { ref, computed, watch } from 'vue'
import type { Ref } from 'vue'
interface UsePagedListOptions
fetcher: (params: { page: number; keyword: string }) => Promise<{ list: T[]; total: number }>
pageSize?: number
}
export function usePagedList
const { fetcher, pageSize = 10 } = options
const keyword = ref('')
const currentPage = ref(1)
const list = ref([]) as Ref
const total = ref(0)
const loading = ref(false)
const totalPages = computed(() => Math.ceil(total.value / pageSize))
const hasMore = computed(() => currentPage.value < totalPages.value)
let timer: ReturnType
const load = async () => {
loading.value = true
try {
const res = await fetcher({
page: currentPage.value,
keyword: keyword.value
})
list.value = res.list
total.value = res.total
} finally {
loading.value = false
}
}
// 关键词变化:重置到第一页并防抖加载
watch(keyword, () => {
currentPage.value = 1
if (timer) clearTimeout(timer)
timer = setTimeout(load, 400)
})
// 翻页立即加载
watch(currentPage, load, { immediate: true })
return {
keyword, currentPage, list, total,
loading, totalPages, hasMore,
reload: load
}
}
组件中使用只需几行,业务代码高度精简:
import { usePagedList } from '@/composables/usePagedList'
import { fetchUsers } from '@/api/user'
const {
keyword, currentPage, list,
loading, totalPages
} = usePagedList({ fetcher: fetchUsers, pageSize: 20 })
{{ currentPage }} / {{ totalPages }}