组合式函数的设计原则
Vue3的组合式API(Composition API)让逻辑复用从mixin的隐式依赖进化为显式的函数组合。composable函数是Vue3生态中最核心的代码组织单元——它封装响应式状态和副作用,通过参数和返回值明确接口契约。这篇文章从设计原则、常见模式到TypeScript类型安全,给出组合式函数的完整工程化实践。
基础模式:状态封装与响应式返回
最简单的composable封装一段可复用的响应式逻辑。以鼠标位置追踪为例:
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
使用方直接解构调用:const { x, y } = useMousePosition()。ref保持响应性,模板中自动解包。关键设计点:副作用(事件监听)在onMounted中注册,onUnmounted中清理,避免内存泄漏。
进阶模式:参数化与配置驱动
实际业务中的composable需要接受参数控制行为。以分页数据请求为例:
import { ref, watch, toValue } from 'vue'
import type { Ref } from 'vue'
interface UsePaginationOptions<T> {
fetcher: (page: number, size: number) => Promise<{ data: T[]; total: number }>
pageSize?: number
immediate?: boolean
}
export function usePagination<T>(options: UsePaginationOptions<T>) {
const currentPage = ref(1)
const pageSize = ref(options.pageSize ?? 20)
const data = ref<T[]>([]) as Ref<T[]>
const total = ref(0)
const loading = ref(false)
const error = ref<Error | null>(null)
async function execute() {
loading.value = true
error.value = null
try {
const result = await options.fetcher(currentPage.value, pageSize.value)
data.value = result.data
total.value = result.total
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
watch([currentPage, pageSize], execute)
if (options.immediate !== false) {
execute()
}
return { data, total, loading, error, currentPage, pageSize, refresh: execute }
}
参数通过接口类型UsePaginationOptions约束,泛型T让返回的data保留元素类型信息。watch自动响应分页参数变化,immediate控制是否立即执行。这种模式覆盖了大部分异步数据场景。
组合模式:composable之间的依赖编排
composable可以组合其他composable,构建更复杂的业务逻辑。以带防抖搜索的分页列表为例:
import { ref, watch } from 'vue'
import { useDebounce } from './useDebounce'
import { usePagination } from './usePagination'
interface SearchResult {
id: string
title: string
}
export function useSearchList(apiUrl: string) {
const keyword = ref('')
const debouncedKeyword = useDebounce(keyword, 300)
const pagination = usePagination<SearchResult>({
fetcher: (page, size) =>
fetch(`${apiUrl}?q=${debouncedKeyword.value}&page=${page}&size=${size}`)
.then(r => r.json()),
pageSize: 10,
})
watch(debouncedKeyword, () => {
pagination.currentPage.value = 1
pagination.refresh()
})
return { keyword, ...pagination }
}
useDebounce提供防抖keyword,usePagination管理分页状态。组合后的useSearchList对外暴露简洁接口,内部各composable各司其职。这种方式比把所有逻辑写在一个setup函数中清晰得多。
TypeScript类型安全:泛型约束与类型推导
composable的TypeScript类型设计要兼顾灵活性和安全性。几个关键实践:
1. Ref解包类型:computed返回ComputedRef<T>,ref返回Ref<T>,在模板中自动解包为T。导出时不要用as const断言,会破坏响应性。
2. 可选参数用MaybeRef<T>:让参数既接受原始值也接受ref:
import type { Ref } from 'vue'
type MaybeRef<T> = T | Ref<T>
export function useFetch<T>(url: MaybeRef<string>) {
// toValue() 同时处理 Ref 和原始值
const resolvedUrl = toValue(url)
// ...
}
3. 返回类型显式标注:复杂的composable返回值建议用interface定义,避免推导丢失ref信息:
interface UseFetchReturn<T> {
data: Readonly<Ref<T | null>>
error: Ref<Error | null>
loading: Ref<boolean>
execute: () => Promise<void>
}
这些类型约束确保composable的接口在编译期就可被发现错误,减少运行时排查成本。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-she-ji-mo-shi-cong-ke-fu-yong-luo-ji/