组合式函数的设计原则
Vue3的Composition API将逻辑复用从Mixin转向了Composables(组合式函数)。一个设计良好的组合式函数应当满足三个条件:单一职责、响应式状态自包含、显式的输入输出。函数内部创建的ref和reactive不应泄漏到外部,外部只通过返回值访问受控的响应式引用。
useRequest:通用异步请求封装
前端项目中最频繁的异步操作是API请求。useRequest需要处理加载状态、错误处理、数据缓存和请求取消四个核心问题:
import { ref, shallowRef, onUnmounted } from "vue"
export function useRequest(fn, options = {}) {
const { immediate = true, initialData = null, debounce = 0 } = options
const data = shallowRef(initialData)
const loading = ref(false)
const error = ref(null)
let controller = null
let timer = null
const execute = async (...args) => {
if (timer) clearTimeout(timer)
const run = async () => {
controller?.abort()
controller = new AbortController()
loading.value = true
error.value = null
try {
data.value = await fn(...args, { signal: controller.signal })
} catch (e) {
if (e.name !== "AbortError") error.value = e
} finally {
loading.value = false
}
}
if (debounce > 0) {
timer = setTimeout(run, debounce)
} else {
await run()
}
}
onUnmounted(() => controller?.abort())
if (immediate) execute()
return { data, loading, error, execute }
}
响应式状态工厂:创建可复用的状态模板
当多个组件需要相同结构但独立的状态(如多标签页各自维护筛选条件),可以用工厂函数生成独立实例:
export function createFilterState(defaults = {}) {
const keyword = ref(defaults.keyword || "")
const status = ref(defaults.status || "all")
const dateRange = ref(defaults.dateRange || [])
const page = ref(1)
const pageSize = ref(20)
const reset = () => {
keyword.value = defaults.keyword || ""
status.value = defaults.status || "all"
dateRange.value = defaults.dateRange || []
page.value = 1
pageSize.value = 20
}
const params = computed(() => ({
keyword: keyword.value,
status: status.value === "all" ? undefined : status.value,
startDate: dateRange.value[0],
endDate: dateRange.value[1],
page: page.value,
pageSize: pageSize.value
}))
watch([keyword, status], () => { page.value = 1 })
return { keyword, status, dateRange, page, pageSize, params, reset }
}
// 在组件中使用,每个实例完全独立
const filterA = createFilterState({ status: "active" })
const filterB = createFilterState({ status: "archived" })
事件通信:useEventBus的轻量实现
跨组件通信不一定需要Pinia,轻量场景用事件总线更直接。Vue3没有内置事件系统,可以用Map组合实现:
import { onUnmounted } from "vue"
const bus = new Map()
export function useEventBus() {
const on = (event, handler) => {
if (!bus.has(event)) bus.set(event, new Set())
bus.get(event).add(handler)
}
const off = (event, handler) => {
bus.get(event)?.delete(handler)
}
const emit = (event, payload) => {
bus.get(event)?.forEach(fn => fn(payload))
}
const useOn = (event, handler) => {
on(event, handler)
onUnmounted(() => off(event, handler))
}
return { on, off, emit, useOn }
}
useOn在组件卸载时自动取消订阅,避免内存泄漏。对于需要持久化的全局状态,仍然推荐Pinia。
组合式函数的测试策略
组合式函数本质是普通JavaScript函数,测试比组件简单得多。使用Vue的withSetup工具在测试中执行setup上下文:
import { withSetup } from "@vue/test-utils"
import { useRequest } from "./useRequest"
test("useRequest handles success", async () => {
const mockFn = vi.fn().mockResolvedValue({ id: 1, name: "test" })
const { data, loading, error } = withSetup(() => useRequest(mockFn))
expect(loading.value).toBe(true)
await vi.waitFor(() => expect(loading.value).toBe(false))
expect(data.value).toEqual({ id: 1, name: "test" })
expect(error.value).toBeNull()
})
组合式函数的单元测试覆盖率应优先于组件测试,因为它们承载了核心业务逻辑,且调试成本低、执行速度快。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-she-ji-mo-shi-cong-userequest-dao/