Vue3组合式函数Composables设计模式与最佳实践

Composables的设计原则与核心概念

Vue3的组合式函数(Composables)是复用有状态逻辑的核心机制。与Vue2的Mixins相比,Composables具有明确的输入输出、不存在命名冲突、来源可追溯等优势。一个设计良好的Composable应当遵循单一职责原则,只封装一个清晰的功能单元。

Composable的命名约定以use开头,返回值始终是ref或reactive对象,确保响应式链路不被打断。当Composable内部需要访问组件实例(如onMounted、provide/inject)时,必须在setup函数或script setup中调用,不能在异步回调中延迟调用。

基础Composable:数据请求与缓存

网络请求是最常见的Composable场景。一个生产级useFetch需要处理加载状态、错误处理、缓存和请求取消:

import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)

let controller = null

async function doFetch() {
data.value = null
error.value = null
loading.value = true

controller?.abort()
controller = new AbortController()

const resolvedUrl = toValue(url)

try {
const res = await fetch(resolvedUrl, { signal: controller.signal })
if (!res.ok) throw new Error(`HTTP ${res.status}`)
data.value = await res.json()
} catch (e) {
if (e.name !== 'AbortError') {
error.value = e
}
} finally {
loading.value = false
}
}

watchEffect(() => {
doFetch()
})

return { data, error, loading, refresh: doFetch }
}

使用toValue()支持传入ref、getter或原始值作为url参数,增强了Composable的灵活性。AbortController确保当url变化触发重新请求时,上一次请求被正确取消,避免竞态条件。

进阶Composable:鼠标追踪与事件节流

对于高频事件(如mousemove、resize),Composable需要内置节流或防抖逻辑,避免性能问题:

import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
const x = ref(0)
const y = ref(0)
let lastTime = 0

function onMouseMove(e) {
const now = performance.now()
if (now - lastTime < 16) return // approx 60fps throttle
lastTime = now
x.value = e.clientX
y.value = e.clientY
}

onMounted(() => window.addEventListener('mousemove', onMouseMove))
onUnmounted(() => window.removeEventListener('mousemove', onMouseMove))

return { x, y }
}

在onUnmounted中清理事件监听器是Composable的关键职责,否则会导致内存泄漏。对于需要清理的副作用,Vue3也提供了onScopeDispose配合EffectScope使用:

import { effectScope, onScopeDispose } from 'vue'

export function useWebSocket(url) {
const scope = effectScope()
const messages = ref([])
let ws

scope.run(() => {
ws = new WebSocket(url)
ws.onmessage = (e) => {
messages.value.push(JSON.parse(e.data))
}

onScopeDispose(() => {
ws.close()
})
})

return { messages, dispose: () => scope.stop() }
}

Composable组合:模块化构建复杂功能

Composable的真正威力在于组合。一个表单处理逻辑可以由多个Composable组合而成:

export function useForm(initialValues, validators) {
const values = ref({ ...initialValues })
const errors = ref({})
const isSubmitting = ref(false)

const debouncedValidate = useDebounceFn(validate, 300)

function validate() {
const result = {}
for (const [key, validator] of Object.entries(validators)) {
const error = validator(values.value[key])
if (error) result[key] = error
}
errors.value = result
return Object.keys(result).length === 0
}

async function handleSubmit(fn) {
if (!validate()) return
isSubmitting.value = true
try {
await fn(values.value)
} finally {
isSubmitting.value = false
}
}

return { values, errors, isSubmitting, validate, handleSubmit }
}

这种组合模式将表单逻辑从组件中完全解耦,组件只需要声明式地绑定values和errors,关注点更加清晰。

Composables测试策略

Composables的单元测试比组件测试更简单,因为Composable是纯函数(从输入到响应式输出的映射)。使用@vue/test-utils的mount组合式函数的方式:

import { withSetup } from './test-utils'
import { useMouse } from './useMouse'

test('useMouse tracks mouse position', () => {
const { x, y } = withSetup(() => useMouse())
expect(x.value).toBe(0)
expect(y.value).toBe(0)

window.dispatchEvent(new MouseEvent('mousemove', { clientX: 100, clientY: 200 }))
expect(x.value).toBe(100)
expect(y.value).toBe(200)
})

withSetup辅助函数创建一个临时组件来执行Composable,并在测试结束后自动清理副作用。这种方式比直接调用Composable更接近真实运行环境,能正确处理onMounted、provide/inject等依赖组件实例的特性。

设计良好的Composables体系能够显著降低Vue3项目的代码重复率,提升逻辑复用性。关键在于每个Composable保持单一职责、正确处理生命周期清理、支持灵活的输入参数类型,并通过组合模式构建复杂功能而非在单个Composable中堆砌逻辑。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-composables-she-ji-mo-shi-yu-zui-jia/

(0)
小编小编
上一篇 12小时前
下一篇 12小时前

相关推荐