组合式API解决的核心问题
Vue2的OptionsAPI在大型项目中最突出的问题是逻辑分散。一个表单组件的验证逻辑、提交逻辑、状态管理散布在data、methods、computed、watch中,改一个功能要在四个地方跳转。组合式API把相关逻辑聚合到一起,一个useXxx函数就是一个完整的关注点。
这不是风格偏好的问题,而是工程可维护性的选择。组件超过200行后,OptionsAPI的维护成本呈指数级增长,组合式API保持线性。
Composable函数设计规范
企业级Composable必须满足五个条件:TypeScript类型完备、参数可响应式、副作用可清理、支持SSR、有完整的单元测试。
以表单管理Composable为例:
// composables/useForm.ts
import { ref, reactive, computed, type Ref } from 'vue'
interface FieldRule<T> {
required?: boolean
min?: number
max?: number
pattern?: RegExp
validator?: (value: T) => string | null
}
interface FieldConfig<T> {
defaultValue: T
rules?: FieldRule<T>[]
}
type FormSchema<T extends Record<string, any>> = {
[K in keyof T]: FieldConfig<T[K]>
}
export function useForm<T extends Record<string, any>>(schema: FormSchema<T>) {
const values = reactive({} as T) as T
const errors = reactive({} as Record<keyof T, string | null>)
const touched = reactive({} as Record<keyof T, boolean>)
for (const key in schema) {
values[key] = schema[key].defaultValue
errors[key] = null
touched[key] = false
}
function validateField(key: keyof T): boolean {
const rules = schema[key].rules || []
const value = values[key]
for (const rule of rules) {
if (rule.required && (value === null || value === undefined || value === '')) {
errors[key] = '此字段为必填项'
return false
}
if (rule.min !== undefined && typeof value === 'string' && value.length < rule.min) {
errors[key] = '最少输入' + rule.min + '个字符'
return false
}
if (rule.validator) {
const msg = rule.validator(value)
if (msg) {
errors[key] = msg
return false
}
}
}
errors[key] = null
return true
}
const isValid = computed(() => {
return Object.keys(errors).every(key => errors[key as keyof T] === null)
})
const isDirty = computed(() => {
return Object.keys(schema).some(key => {
return values[key] !== schema[key].defaultValue
})
})
return { values, errors, touched, isValid, isDirty, validateField }
}
组件通信模式进阶
Props + Emits适合父子通信,跨层级通信推荐Provide/Inject + Symbol Key:
// composables/useTableContext.ts
import { inject, provide, type InjectionKey } from 'vue'
interface TableContext {
selectedKeys: Ref<Set<string>>
toggleRow: (key: string) => void
pageSize: Ref<number>
currentPage: Ref<number>
}
const TABLE_KEY: InjectionKey<TableContext> = Symbol('table-context')
export function provideTableContext(ctx: TableContext) {
provide(TABLE_KEY, ctx)
}
export function useTableContext(): TableContext {
const ctx = inject(TABLE_KEY)
if (!ctx) {
throw new Error('useTableContext must be used within Table component')
}
return ctx
}
Symbol Key保证类型安全且不会冲突,inject失败时抛出明确错误而非返回undefined让下游崩溃。
逻辑复用的分层架构
企业级前端应用的逻辑复用分三层:
基础设施层:useRequest、useStorage、useEventBus——与UI无关的通用逻辑
业务逻辑层:useAuth、usePermission、useCart——与业务领域绑定的逻辑
UI交互层:useForm、useTable、useModal——与UI组件绑定的交互逻辑
// 基础设施层:通用请求
export function useRequest<T>(fetcher: () => Promise<T>) {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<Error | null>(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
data.value = await fetcher()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
return { data, error, loading, execute }
}
// 业务逻辑层:用户权限
export function useAuth() {
const { data: user, execute: fetchUser } = useRequest(() =>
api.getCurrentUser()
)
const isAdmin = computed(() => user.value?.role === 'admin')
function hasPermission(perm: string): boolean {
return user.value?.permissions?.includes(perm) ?? false
}
return { user, isAdmin, hasPermission, fetchUser }
}
// UI交互层:列表分页
export function usePagination(fetchFn: (page: number, size: number) => Promise<any>) {
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const { data, loading, execute } = useRequest(() =>
fetchFn(currentPage.value, pageSize.value)
)
function changePage(page: number) {
currentPage.value = page
execute()
}
return { data, loading, currentPage, pageSize, total, changePage }
}
三层分离让每一层都可以独立测试、独立复用。usePagination不关心数据从哪来,useAuth不关心数据怎么展示,useRequest不关心业务是什么。
组件设计的边界原则
什么逻辑该放组件内,什么该抽Composable?判断标准:
出现两次就抽——相同逻辑在两个组件中出现,立即抽取,不要等三次。
超过50行就抽——script setup超过50行,必有可抽取的逻辑。
有副作用就抽——涉及API调用、DOM操作、定时器、事件监听,必须抽到Composable中统一管理生命周期。
纯展示逻辑留组件内——只涉及当前组件UI状态的逻辑(展开折叠、hover状态)没必要抽。
Vue3组合式API的设计价值不在于”新写法”,而在于把散落在OptionsAPI各处的逻辑重新聚合成内聚的函数单元,每个Composable只做一件事,做好一件事。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-qi-ye-ji-zu-jian-she-ji-mo-shi-cong-luo/