组合函数(Composables)
约 944 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › Composition API › 组合函数(Composables)
组合函数(Composables)是 Vue 3 Composition API 最强大的复用机制——以 use 开头的函数,内部使用 Composition API 封装可复用逻辑。
为什么需要 Composables
Mixin 的痛点
Vue 2 用 Mixin 复用逻辑,但有明显缺陷:
// mixin/userMixin.js
export default {
data: () => ({ user: null, loading: false }),
created() { this.fetchUser() },
methods: { fetchUser() { ... } }
}
// ❌ 混入多个 mixin 时,属性来源不明
// ❌ mixin 内部的同名属性/方法会冲突
// ❌ 无法在 mixin 之间互相通信Composables 的改进
<script setup>
// ✅ 显式调用,来源清晰
const { user, loading } = useUser()
const { formatDate } = useDateFormat()
// ✅ 可以互相组合
const { posts } = useUserPosts(user)
</script>基础示例:useCounter
<script setup>
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
function increment(n = 1) { count.value += n }
function decrement(n = 1) { count.value -= n }
function reset() { count.value = initialValue }
return { count, double, increment, decrement, reset }
}
</script>在组件中使用:
<script setup>
import { useCounter } from './composables/useCounter'
// 每个组件实例独立的状态
const { count, double, increment } = useCounter(10)
</script>
<template>
<p>{{ count }} × 2 = {{ double }}</p>
<button @click="increment">+1</button>
</template>实战:useFetch
<script setup>
// composables/useFetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(true)
// watchEffect 自动追踪 url 的变化
watchEffect(async () => {
loading.value = true
error.value = null
try {
// toValue() 处理 ref 或普通值的 url
const res = await fetch(toValue(url))
if (!res.ok) throw new Error(`HTTP ${res.status}`)
data.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
return { data, error, loading }
}
</script>使用:
<script setup>
import { ref, computed } from 'vue'
import { useFetch } from './composables/useFetch'
const page = ref(1)
const url = computed(() => `/api/users?page=${page.value}`)
const { data, loading, error } = useFetch(url)
</script>
<template>
<p v-if="loading">加载中...</p>
<p v-else-if="error">{{ error }}</p>
<ul v-else>
<li v-for="user in data" :key="user.id">{{ user.name }}</li>
</ul>
<button @click="page++">下一页</button>
</template>实战:useMouse
<script setup>
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
</script>使用:
<script setup>
import { useMouse } from './composables/useMouse'
const { x, y } = useMouse()
</script>
<template>
<p>鼠标位置: {{ x }}, {{ y }}</p>
</template>实战:useLocalStorage
<script setup>
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const data = ref(JSON.parse(localStorage.getItem(key)) ?? defaultValue)
// 自动同步到 localStorage
watch(data, (val) => {
localStorage.setItem(key, JSON.stringify(val))
}, { deep: true })
return data
}
</script>使用:
<script setup>
import { useLocalStorage } from './composables/useLocalStorage'
const theme = useLocalStorage('theme', 'light')
const todos = useLocalStorage('todos', [])
</script>组合式:组合多个 Composables
<script setup>
// composables/useUser.js
import { toValue } from 'vue'
import { useFetch } from './useFetch'
import { useLocalStorage } from './useLocalStorage'
export function useUser(userId) {
const token = useLocalStorage('token', '')
const { data: user, loading, error } = useFetch(
() => `/api/users/${toValue(userId)}`
)
function isLoggedIn() {
return !!token.value
}
return { user, loading, error, isLoggedIn }
}
</script>自定义 Composables 注意事项
命名规则
- 必须以
use开头(Vue 官方约定) - 描述性:
useFetch、useMouse、useLocalStorage
不要在条件中调用
与 hooks 规则类似——composables 不能写在条件/循环中:
<script setup>
// ❌ 错误
if (isLogged.value) {
const { user } = useUser() // Composition API 函数需要在 setup 顶层调用
}
// ✅ 正确
const user = isLogged.value ? useUser() : null // 返回值在条件中处理
</script>返回值可任意选择
// 返回对象(推荐:按需解构)
return { count, increment }
// 返回单值(当只有一个返回值时)
return data
// 只读/可写区分
return { count: readonly(count), increment } // 外部无法修改 countComposables vs 其他复用方式
| 方式 | 优势 | 劣势 |
|---|---|---|
| Mixin | 简单直接 | 命名冲突、来源不明、隐式依赖 |
| 无渲染组件(作用域插槽) | 模板灵活 | 额外组件嵌套、逻辑分散 |
| Composables | ✅ 来源清晰、无冲突、可组合、类型安全 | 学习曲线(Composition API) |
小结
| 内容 | 关键点 |
|---|---|
| Composables | useXxx 函数,内部用 Composition API 封装逻辑 |
| 优势 | 来源清晰、无命名冲突、可自由组合 |
| 命名 | 必须 use 开头 |
| 参数 | 可以接收 ref,用 toValue() 统一处理 |
| 组合 | Composables 之间可以自由调用组合 |
上一节:生命周期与模板 ref 下一节:render 函数与 JSX
