Vue3 Composables组合式函数是Vue3组合式API的核心设计模式,通过将组件逻辑封装为可复用的函数,解决Options API在复杂组件中逻辑分散、难以复用的问题。响应式布局、跨端小程序开发等场景中,Composables已经成为Vue3生态的标准逻辑复用方案。组件template中调用的数据和方法,在Composables中通过ref、reactive、computed等响应式API实现,函数返回响应式数据供组件消费。
组合式API响应式基础:ref与reactive
ref和reactive是Vue3响应式系统的两个基础API。ref用于包装基本类型值,通过.value访问和修改。reactive用于包装对象,返回Proxy代理对象,直接操作属性即可触发响应式更新。选择规则:基本类型用ref,对象用reactive,从API返回的响应式对象建议用ref包裹便于整体替换。
import { ref, reactive, computed } from 'vue'
// ref示例:计数器
const count = ref(0)
count.value++
console.log(count.value) // 1
// reactive示例:表单状态
const form = reactive({
username: '',
password: '',
remember: false
})
form.username = 'admin'
// computed示例:派生状态
const isValid = computed(() =>
form.username.length >= 3 && form.password.length >= 6
)
自定义Composable函数设计规范
Composable函数遵循统一命名规范:以use开头,返回响应式数据和方法。一个设计良好的Composable应当职责单一、无副作用、可独立测试。以下是一个完整的useFetch实现,封装数据请求逻辑,包含loading状态、错误处理和数据缓存。
import { ref, onUnmounted } from 'vue'
export function useFetch(url, options = {}) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
let controller = null
async function execute() {
loading.value = true
error.value = null
controller = new AbortController()
try {
const resp = await fetch(url, {
...options,
signal: controller.signal
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
data.value = await resp.json()
} catch (e) {
if (e.name !== 'AbortError') {
error.value = e.message
}
} finally {
loading.value = false
}
}
function cancel() {
if (controller) controller.abort()
}
// 组件卸载时取消未完成请求
onUnmounted(() => cancel())
execute()
return { data, error, loading, refetch: execute, cancel }
}
使用时在组件setup中直接调用,返回的响应式数据可在template中直接使用。onUnmounted生命周期钩子确保组件销毁时清理资源,避免内存泄漏。
逻辑复用模式:鼠标跟踪与窗口尺寸监听
Composables最大的价值在于逻辑复用。多个组件需要相同功能时,将逻辑提取到Composable函数中,各组件独立调用互不干扰。以下示例封装鼠标位置追踪和窗口尺寸监听两个常见场景。
import { ref, onMounted, onUnmounted } from 'vue'
// 鼠标位置追踪
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// 窗口尺寸监听(带防抖)
export function useWindowSize(debounce = 200) {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
let timer = null
function update() {
clearTimeout(timer)
timer = setTimeout(() => {
width.value = window.innerWidth
height.value = window.innerHeight
}, debounce)
}
onMounted(() => window.addEventListener('resize', update))
onUnmounted(() => {
window.removeEventListener('resize', update)
clearTimeout(timer)
})
return { width, height }
}
每个组件调用useMouse()会获得独立的x、y响应式引用,事件监听器的注册和清理由Composable内部管理。这种模式比Vue2的Mixin方案更清晰,不存在命名冲突和数据来源不透明的问题。
Composable组合与状态共享
多个Composable可以自由组合,形成更复杂的业务逻辑。当多个组件需要共享同一份状态时,可以将Composable的响应式状态提升到模块作用域,实现跨组件共享。
import { ref, computed } from 'vue'
// 全局共享的购物车状态(模块级单例)
const cart = ref([])
const cartOpen = ref(false)
export function useCart() {
const totalItems = computed(() =>
cart.value.reduce((sum, item) => sum + item.qty, 0)
)
const totalPrice = computed(() =>
cart.value.reduce((sum, item) => sum + item.qty * item.price, 0)
)
function addItem(product, qty = 1) {
const existing = cart.value.find(i => i.id === product.id)
if (existing) {
existing.qty += qty
} else {
cart.value.push({ ...product, qty })
}
}
function removeItem(id) {
cart.value = cart.value.filter(i => i.id !== id)
}
function clear() {
cart.value = []
}
return {
cart, cartOpen, totalItems, totalPrice,
addItem, removeItem, clear
}
}
由于cart和cartOpen定义在模块作用域,所有调用useCart()的组件共享同一份状态,修改一处所有组件同步更新。这种模式适用于小型应用的简单状态共享。对于复杂的大型应用,推荐使用Pinia状态管理库,它基于组合式API设计,Devtools集成度更好,TypeScript类型推导更完善。
Composables设计的一个常见误区是将所有逻辑塞进一个函数。更好的做法是按功能拆分为多个小函数,再在组件中按需组合。单个Composable保持在50行以内,职责清晰,可测试性强,符合前端工程化的模块化设计原则。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3composables-zu-he-shi-han-shu-she-ji-mo-shi-yu-luo-ji/