Composables设计原则与封装模式
Vue3组合式API的核心优势在于逻辑复用。Composables(组合式函数)取代了Vue2的Mixins,解决了命名冲突和数据来源不透明的痛点。一个规范的Composable遵循use前缀命名,接受ref或getter作为参数,返回响应式状态和方法。前端开发中善用Composables可以把复杂业务逻辑从组件中解耦,组件文件从动辄300行压缩到50行以内。
基础Composable封装模式
// useRequest - 通用异步请求封装
import { ref, readonly } from 'vue'
export function useRequest(fetcher, options = {}) {
const {
immediate = true,
initialData = null,
onSuccess,
onError
} = options
const data = ref(initialData)
const loading = ref(false)
const error = ref(null)
async function execute(...args) {
loading.value = true
error.value = null
try {
data.value = await fetcher(...args)
onSuccess?.(data.value)
} catch (e) {
error.value = e
onError?.(e)
} finally {
loading.value = false
}
}
if (immediate) execute()
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
execute,
refresh: execute
}
}
// 组件中使用
const { data: users, loading, execute: fetchUsers } = useRequest(
() => api.get('/api/users'),
{ initialData: [] }
)
高级模式:带防抖的搜索Composable
// useDebounceSearch - 防抖搜索 + 请求取消
import { ref, watch, onUnmounted } from 'vue'
export function useDebounceSearch(searchFn, delay = 300) {
const query = ref('')
const results = ref([])
const searching = ref(false)
let timer = null
let abortController = null
async function doSearch(keyword) {
// 取消上一次未完成的请求
abortController?.abort()
abortController = new AbortController()
if (!keyword.trim()) {
results.value = []
return
}
searching.value = true
try {
results.value = await searchFn(keyword, {
signal: abortController.signal
})
} catch (e) {
if (e.name !== 'AbortError') {
console.error('Search failed:', e)
}
} finally {
searching.value = false
}
}
watch(query, (val) => {
clearTimeout(timer)
timer = setTimeout(() => doSearch(val), delay)
})
onUnmounted(() => {
clearTimeout(timer)
abortController?.abort()
})
return { query, results, searching }
}
// 使用示例
const { query, results, searching } = useDebounceSearch(
async (keyword, { signal }) => {
const resp = await fetch(`/api/search?q=${keyword}`, { signal })
return resp.json()
},
500
)
响应式性能陷阱与优化
Vue3的响应式系统基于Proxy,但不当使用会产生不必要的依赖追踪和重复渲染:
// ❌ 常见性能陷阱
// 1. 在模板中使用复杂计算属性
// 每次响应式数据变化都会重新执行
const heavyList = computed(() => {
return data.value.map(item => ({
...item,
processed: expensiveTransform(item) // 昂贵操作放computed
}))
})
// ✅ 优化:用shallowRef + 手动触发
const heavyList = shallowRef([])
watch(data, async (newData) => {
// 耗时计算放到Web Worker
heavyList.value = await workerProcess(newData)
}, { immediate: true })
// 2. ❌ 大数组整体响应式
const bigArray = reactive(createLargeArray(10000))
// 任何元素变化都触发整个数组依赖更新
// ✅ 优化:使用markRaw冻结不需要响应式的对象
import { markRaw } from 'vue'
const items = ref(data.map(item => ({
id: item.id,
data: markRaw(item) // 大对象不参与响应式追踪
})))
// 3. ❌ v-for没有key或使用index作key
<div v-for="(item, index) in list" :key="index">
// ✅ 使用唯一ID作key
<div v-for="item in list" :key="item.id">
Composable单元测试方法
// useRequest.test.js
import { useRequest } from './useRequest'
// 使用@vue/test-utils的withSetup辅助函数
function withSetup(composable) {
let result
const app = createApp({
setup() {
result = composable()
return () => {}
}
})
const root = document.createElement('div')
app.mount(root)
return { result, app }
}
test('useRequest - 成功请求', async () => {
const mockData = { id: 1, name: 'test' }
const fetcher = jest.fn().mockResolvedValue(mockData)
const { result } = withSetup(() => useRequest(fetcher))
expect(result.loading.value).toBe(true)
await flushPromises()
expect(result.data.value).toEqual(mockData)
expect(result.loading.value).toBe(false)
expect(result.error.value).toBe(null)
expect(fetcher).toHaveBeenCalledTimes(1)
})
test('useRequest - 请求失败', async () => {
const fetcher = jest.fn().mockRejectedValue(new Error('Network Error'))
const { result } = withSetup(() => useRequest(fetcher, { immediate: false }))
await result.execute()
await flushPromises()
expect(result.error.value.message).toBe('Network Error')
expect(result.data.value).toBe(null)
})
Composables是Vue3生态的逻辑复用基石。封装时保持单一职责,每个Composable只做一件事;返回值用readonly包裹防止外部意外修改;副作用(定时器、事件监听)必须在onUnmounted中清理。遵循这些规则,Composables才能真正实现即插即用。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-composables-feng-zhuang-mo-shi-yu/