引言
在 Vue 3 的 Composition API 中,组合式函数(Composables) 是代码复用的核心模式。它将逻辑封装成可复用的函数,让组件更加简洁、可测试、可维护。本文将深入讲解组合式函数的设计模式、实战技巧以及常见应用场景。
什么是组合式函数?
组合式函数(Composables)是一种基于 Vue 3 Composition API 的代码组织模式。它本质上是一个以 use 开头的函数,内部调用 Vue 的响应式 API(如 ref、reactive、computed、watch 等),返回响应式状态和方法供组件使用。
核心特点
- 以
use开头 :遵循约定,如useCounter、useFetch - 返回响应式数据 :返回
ref、reactive或计算属性 - 无组件实例依赖 :可以在任何地方调用,不依赖
this - 可组合、可复用:多个组合式函数可以组合使用
基础示例
1. 简单的计数器组合式函数
javascript
<!-- useCounter.ts -->
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
increment,
decrement,
reset
}
}
xml
<!-- 组件中使用 -->
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './useCounter'
const { count, increment, decrement, reset } = useCounter(10)
</script>
2. 带参数的组合式函数
typescript
<!-- useLocalStorage.ts -->
import { ref, watch } from 'vue'
export function useLocalStorage(key: string, initialValue: any) {
// 从 localStorage 读取值
const storedValue = localStorage.getItem(key)
const value = ref(storedValue !== null ? JSON.parse(storedValue) : initialValue)
// 监听变化并同步到 localStorage
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
})
const setValue = (newVal: any) => {
value.value = newVal
}
const removeValue = () => {
localStorage.removeItem(key)
value.value = initialValue
}
return {
value,
setValue,
removeValue
}
}
高级应用场景
1. 网络请求组合式函数
typescript
<!-- useFetch.ts -->
import { ref, computed } from 'vue'
interface UseFetchOptions {
immediate?: boolean
headers?: Record<string, string>
}
export function useFetch(url: string, options: UseFetchOptions = {}) {
const { immediate = true, headers = {} } = options
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetched = computed(() => !loading.value && !error.value && data.value !== null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url, { headers })
if (!response.ok) {
throw new Error(`HTTP 错误:${response.status}`)
}
data.value = await response.json()
} catch (err: any) {
error.value = err.message
} finally {
loading.value = false
}
}
if (immediate) {
fetchData()
}
return {
data,
loading,
error,
fetched,
fetchData
}
}
xml
<!-- 组件中使用 -->
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误:{{ error }}</div>
<div v-else-if="fetched">
<h2>{{ data.title }}</h2>
<p>{{ data.body }}</p>
</div>
</div>
</template>
<script setup>
import { useFetch } from './useFetch'
const { data, loading, error, fetched } = useFetch(
'https://jsonplaceholder.typicode.com/posts/1'
)
</script>
2. 防抖和节流组合式函数
typescript
<!-- useDebounce.ts -->
import { ref, watch, type Ref } from 'vue'
export function useDebounce<T>(source: Ref<T>, delay: number = 300) {
const debouncedValue = ref(source.value)
let timer: ReturnType<typeof setTimeout> | null = null
watch(source, (newValue) => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
})
return debouncedValue
}
typescript
<!-- useThrottle.ts -->
import { ref, watch, type Ref } from 'vue'
export function useThrottle<T>(source: Ref<T>, limit: number = 500) {
const throttledValue = ref(source.value)
let lastCall = 0
let timer: ReturnType<typeof setTimeout> | null = null
watch(source, (newValue) => {
const now = Date.now()
if (now - lastCall >= limit) {
throttledValue.value = newValue
lastCall = now
} else {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
throttledValue.value = newValue
lastCall = Date.now()
}, limit - (now - lastCall))
}
})
return throttledValue
}
3. 窗口尺寸组合式函数
javascript
<!-- useWindowSize.ts -->
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const updateSize = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => {
window.addEventListener('resize', updateSize)
})
onUnmounted(() => {
window.removeEventListener('resize', updateSize)
})
return { width, height }
}
4. 鼠标位置组合式函数
javascript
<!-- useMousePosition.ts -->
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
const updatePosition = (event: MouseEvent) => {
x.value = event.clientX
y.value = event.clientY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
return { x, y }
}
组合多个组合式函数
组合式函数的强大之处在于可以相互组合:
javascript
<!-- useSearch.ts -->
import { ref, computed } from 'vue'
import { useDebounce } from './useDebounce'
import { useFetch } from './useFetch'
export function useSearch(apiUrl: string) {
const searchTerm = ref('')
const debouncedSearch = useDebounce(searchTerm, 300)
const {
data: results,
loading,
error
} = useFetch(
computed(() => `${apiUrl}?q=${debouncedSearch.value}`),
{ immediate: false }
)
const search = () => {
// 触发搜索
}
const hasResults = computed(() => results.value && results.value.length > 0)
return {
searchTerm,
results,
loading,
error,
hasResults,
search
}
}
最佳实践
1. 命名规范
- 以
use开头,如useCounter、useFetch - 函数名清晰表达功能
- 返回对象使用有意义的属性名
2. 类型安全
使用 TypeScript 提供完整的类型支持:
typescript
interface UseCounterReturn {
count: Ref<number>
increment: () => void
decrement: () => void
reset: () => void
}
export function useCounter(initialValue: number = 0): UseCounterReturn {
// ...
}
3. 解构保持响应性
使用 toRefs 确保解构后保持响应性:
php
<!-- useStore.ts -->
import { reactive, toRefs } from 'vue'
export function useStore() {
const state = reactive({
user: null,
token: null,
loading: false
})
return toRefs(state)
}
4. 错误处理
在组合式函数内部处理错误,返回错误状态:
typescript
export function useAsync(fn: () => Promise<any>) {
const loading = ref(false)
const error = ref(null)
const data = ref(null)
const execute = async () => {
loading.value = true
error.value = null
try {
data.value = await fn()
} catch (err: any) {
error.value = err.message
} finally {
loading.value = false
}
}
return { loading, error, data, execute }
}
总结
组合式函数是 Vue 3 中最强大的代码复用模式。通过:
- 逻辑封装:将相关功能打包成独立函数
- 响应式集成:无缝使用 Vue 的响应式系统
- 可组合性:多个组合式函数可以相互组合
- 类型安全:完整的 TypeScript 支持
掌握组合式函数,能让你的 Vue 代码更加简洁、可维护、可测试。建议从简单的功能开始实践,逐步构建自己的组合式函数库。