Vue 3 组合式函数(Composables)全面解析:从原理到实战

一、前言

当 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 世界里的「逻辑积木」------

你可以自由拼接、拆解、组合它们,构建出任何复杂的交互逻辑。

如果你曾觉得逻辑在组件里越堆越乱,

那是时候开始用 组合式函数 让代码"呼吸"了。

相关推荐
XIE3925 分钟前
TipKit:开源富文本编辑器套件,一套逻辑,任意风格!
前端·笔记·开源
范什么特西19 分钟前
一些常用名词
开发语言·前端·javascript
晴天1621 分钟前
前端打包工具全景解析
前端
晴天1622 分钟前
Vite vs Webpack 全方位对比
前端·webpack·node.js
体毛旺盛的猿25 分钟前
HarmonyOS开发面试题
前端·华为·harmonyos
anOnion39 分钟前
构建无障碍组件之Landmarks Pattern
前端·html·交互设计
牧瀬クリスだ40 分钟前
前端三件套基础之JS!!!!
前端·javascript
风骏时光牛马43 分钟前
提示词工程:大模型交互的核心塑造技术
前端
单线程_011 小时前
从案例分析 Vue3 Tokenizer+Parser 源码四
前端·javascript·vue.js
1 小时前
Python 图片处理:裁剪缩放至300×200,格式JPG/PNG/GIF,控制大小≤100KB
linux·前端·python