一、前言
当 Vue 3 发布时,组合式 API(Composition API) 带来了一个革命性的变化:
我们不再需要依赖 data、methods、computed 这些分散的选项,而是能用函数的方式,灵活组织逻辑。
这套函数化逻辑复用方案,就叫做 组合式函数(Composables) 。
简单来说:
-
Options API 更像是"配置式";
-
Composition API 则让我们"像写逻辑一样组织组件"。
而 组合式函数(Composables) ,就是在这个新体系下,用于封装和复用有状态逻辑的函数。
二、什么是组合式函数?
先来看一句官方定义:
"组合式函数是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。"
也就是说,它不仅可以处理计算逻辑、请求接口、事件监听,还能和组件生命周期绑定,并且是响应式的。
按照惯例,我们命名时一般以 use 开头:
js
// useXxx 组合式函数命名惯例
export function useMouse() { ... }
export function useFetch() { ... }
export function useEventListener() { ... }
三、基础示例:从组件逻辑到组合式函数
假设我们要做一个"鼠标追踪器",实时显示鼠标位置。
如果直接写在组件里,可能是这样 👇
js
<!-- MouseComponent.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 定义响应式状态
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))
</script>
<template>
鼠标坐标:{{ x }}, {{ y }}
</template>
很好,但如果我们多个页面都要复用这个逻辑呢?
那就应该把它抽出来!
四、封装成组合式函数
js
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 约定:组合式函数以 use 开头
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 }
}
使用起来非常简单:
js
<!-- MouseComponent.vue -->
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>
鼠标坐标:{{ x }}, {{ y }}
</template>
✅ 这样写的好处是:
- 组件逻辑更清晰;
- 多处可复用;
- 生命周期自动关联;
- 每个组件都拥有独立的状态(互不干扰)。
五、进阶封装:useEventListener
假如我们还想监听滚动、键盘等事件,可以进一步抽象出一个事件监听函数 👇
js
// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
接着 useMouse 就能进一步简化:
js
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (e) => {
x.value = e.pageX
y.value = e.pageY
})
return { x, y }
}
💡 这样我们不仅复用了逻辑,还建立了逻辑的"组合关系" ------
组合式函数可以嵌套调用另一个组合式函数。
六、异步场景:useFetch 示例
除了事件逻辑,我们常常需要封装"异步请求逻辑",比如:
js
// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
使用方式:
js
<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('https://api.example.com/posts')
</script>
<template>
<div v-if="error">❌ 出错:{{ error.message }}</div>
<div v-else-if="data">✅ 数据:{{ data }}</div>
<div v-else>⏳ 加载中...</div>
</template>
七、响应式参数:动态请求的 useFetch
上面 useFetch 只会执行一次,
但如果我们希望在 URL 改变时自动重新请求呢?
就可以用 watchEffect() + toValue():
js
// fetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const fetchData = () => {
data.value = null
error.value = null
fetch(toValue(url)) // 兼容 ref / getter / 普通字符串
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
watchEffect(() => {
fetchData() // url 改变时会自动重新执行
})
return { data, error }
}
使用示例:
js
<script setup>
import { ref } from 'vue'
import { useFetch } from './fetch.js'
const postId = ref(1)
const { data, error } = useFetch(() => `/api/posts/${postId.value}`)
// 模拟切换文章
function nextPost() {
postId.value++
}
</script>
<template>
<button @click="nextPost">下一篇</button>
<div v-if="error">❌ 出错:{{ error.message }}</div>
<div v-else-if="data">📰 文章:{{ data.title }}</div>
<div v-else>⏳ 加载中...</div>
</template>
✅ 这就让你的 useFetch 成为了真正"响应式的请求函数"。
八、组合式函数的使用规范
项目 | 推荐做法 | 原因 |
---|---|---|
🧩 命名 | useXxx() | 一目了然,符合惯例 |
📦 返回值 | 返回多个 ref,不要直接返回 reactive 对象 | 防止解构时丢失响应性 |
🔁 生命周期 | 必须在 | Vue 需要绑定当前组件实例 |
⚙️ 参数 | 建议使用 toValue() 规范化输入 | 兼容 ref、getter、普通值 |
🧹 清理 | 要在 onUnmounted() 清理副作用 | 避免内存泄漏 |
九、与其他模式的比较
模式 | 优点 | 缺点 |
---|---|---|
Mixins | 逻辑复用简单 | 来源不清晰、命名冲突、隐式依赖 |
无渲染组件 (Renderless) | 可复用逻辑 + UI | 会额外创建组件实例,性能差 |
组合式函数 (Composables) | 无实例开销、逻辑清晰、依赖显式 | 不直接提供模板复用 |
✅ 结论:
纯逻辑复用 → 用组合式函数
逻辑 + UI 复用 → 用无渲染组件
十、总结
概念 | 说明 |
---|---|
组合式函数 | 利用 Vue 组合式 API 封装可复用逻辑的函数 |
核心特性 | 可使用 ref / reactive / 生命周期钩子 / watch |
优势 | 灵活组合、逻辑清晰、性能优秀、类型友好 |
常见应用 | 请求封装、事件监听、滚动追踪、权限控制、表单管理等 |
开发建议 | 命名统一、输入规范化、注意生命周期上下文 |
✨ 最后
Composables 就像是 Vue 世界里的「逻辑积木」------
你可以自由拼接、拆解、组合它们,构建出任何复杂的交互逻辑。
如果你曾觉得逻辑在组件里越堆越乱,
那是时候开始用 组合式函数 让代码"呼吸"了。