Vue3组合式API解决了Options API的什么问题
Options API按选项组织代码(data、methods、computed、watch分散在不同选项里),一个功能的逻辑被拆成好几块,组件超过300行就难以维护。组合式API用Composables把同一功能的逻辑聚合到一起,多个组件间复用只需调用一个函数。这篇文章用实际项目案例演示从Options API到Composables的重构过程,以及TypeScript实战中的类型约束写法。
Options API到Composition API的基础迁移
迁移前的Options API写法:
export default {
data() {
return {
userList: [],
loading: false,
searchKey: '',
currentPage: 1,
pageSize: 10,
total: 0,
}
},
computed: {
filteredUsers() {
return this.userList.filter(u => u.name.includes(this.searchKey))
}
},
methods: {
async fetchUsers() {
this.loading = true
try {
const res = await api.getUsers({ page: this.currentPage, size: this.pageSize })
this.userList = res.data.list
this.total = res.data.total
} finally {
this.loading = false
}
},
handleSearch() {
this.currentPage = 1
this.fetchUsers()
}
},
mounted() {
this.fetchUsers()
}
}
迁移后的Composition API写法:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
const userList = ref<User[]>([])
const loading = ref(false)
const searchKey = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const filteredUsers = computed(() =>
userList.value.filter(u => u.name.includes(searchKey.value))
)
async function fetchUsers() {
loading.value = true
try {
const res = await api.getUsers({ page: currentPage.value, size: pageSize.value })
userList.value = res.data.list
total.value = res.data.total
} finally {
loading.value = false
}
}
function handleSearch() {
currentPage.value = 1
fetchUsers()
}
onMounted(() => fetchUsers())
</script>
功能一致,但所有相关变量和函数在同一个作用域内,阅读时不需要在data/computed/methods之间跳转。
提取Composables实现逻辑复用
当多个组件都需要用户列表的分页加载能力时,把它抽成Composable:
// composables/useUserList.ts
import { ref, computed } from 'vue'
import type { User } from '@/types'
export function useUserList() {
const userList = ref<User[]>([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
async function fetchUsers(params?: Partial<{ keyword: string }>) {
loading.value = true
try {
const res = await api.getUsers({
page: currentPage.value,
size: pageSize.value,
...params,
})
userList.value = res.data.list
total.value = res.data.total
} finally {
loading.value = false
}
}
function changePage(page: number) {
currentPage.value = page
fetchUsers()
}
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
return {
userList,
loading,
currentPage,
pageSize,
total,
totalPages,
fetchUsers,
changePage,
}
}
组件中使用:
<script setup lang="ts">
import { useUserList } from '@/composables/useUserList'
import { onMounted } from 'vue'
const { userList, loading, totalPages, currentPage, changePage, fetchUsers } = useUserList()
onMounted(() => fetchUsers())
</script>
通用分页Composables抽离
分页逻辑不仅用于用户列表,还用于订单、商品等场景。进一步抽象:
// composables/usePagination.ts
import { ref, computed, type Ref } from 'vue'
interface PaginationOptions<T> {
fetchFn: (params: { page: number; size: number; [k: string]: any }) => Promise<{ list: T[]; total: number }>
pageSize?: number
}
export function usePagination<T>(options: PaginationOptions<T>) {
const list = ref<T[]>([]) as Ref<T[]>
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(options.pageSize || 10)
const total = ref(0)
async function fetch(extraParams?: Record<string, any>) {
loading.value = true
try {
const res = await options.fetchFn({
page: currentPage.value,
size: pageSize.value,
...extraParams,
})
list.value = res.list
total.value = res.total
} finally {
loading.value = false
}
}
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
function goToPage(page: number) {
currentPage.value = page
fetch()
}
return { list, loading, currentPage, pageSize, total, totalPages, fetch, goToPage }
}
任何需要分页的组件,三行代码接入:
const { list, loading, totalPages, currentPage, goToPage, fetch } = usePagination({
fetchFn: (params) => api.getOrders(params).then(r => ({ list: r.data.list, total: r.data.total })),
pageSize: 20,
})
TypeScript类型约束与前端工程化
Composables配合TypeScript的关键是泛型约束。上面的usePagination通过泛型T约束列表数据类型,组件调用时自动推导出list的类型。
类型定义文件组织:
// types/index.ts
export interface User {
id: number
name: string
email: string
role: 'admin' | 'editor' | 'viewer'
createdAt: string
}
export interface PaginatedResponse<T> {
list: T[]
total: number
}
工程化层面,Composables目录结构:
src/
composables/
usePagination.ts
useUserList.ts
useAuth.ts
useTheme.ts
types/
index.ts
views/
UserList.vue
OrderList.vue
ESLint规则建议开启:vue/component-api-style强制使用<script setup>,vue/no-ref-as-operand防止ref误用。
响应式布局下的Composables设计
移动端和PC端可能需要不同的分页策略,Composables可以封装响应式逻辑:
import { computed } from 'vue'
import { useBreakpoints } from '@/composables/useBreakpoints'
export function useResponsivePagination() {
const { isMobile } = useBreakpoints()
const mobilePageSize = 5
const desktopPageSize = 20
const pageSize = computed(() => isMobile.value ? mobilePageSize : desktopPageSize)
return { pageSize }
}
从Options API迁移到Composables不是简单的语法转换,而是代码组织方式的升级。核心原则:一个Composables只做一件事,函数签名清晰,返回值显式声明类型。这套方案在团队协作项目中显著降低代码理解成本,新人接手一个模块只需要看对应的Composables文件,不用翻遍整个组件。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-shi-zhan-cong-optionsapi-qian-yi-dao/