首页 / Vue3 入门教程 / 组合式函数(Composables)

Vue3 入门教程

组合式函数(Composables)

本教程共 40 篇 · 第 29 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程Composables组合式函数逻辑复用

29. 组合式函数(Composables)

本节目标:搞清楚什么是组合式函数,以及如何用它把”有状态”的逻辑变成独立可复用的模块。

什么是组合式函数

组件复用是 Vue 的强项。但有些逻辑跟”组件”无关——它不涉及 UI,只管追踪鼠标位置、管理网络状态、处理定时器……这类逻辑如果塞进组件,换个项目又得重写一遍。

组合式函数(Composable) 就是解决这个问题的:一个利用了 Composition API 的普通 JavaScript 函数,专门封装”有状态的逻辑”。

有状态逻辑 = 需要随着时间变化来管理状态的逻辑。比如鼠标坐标、屏幕宽度、WebSocket 连接状态。

组合式函数的命名有约定:以 use 开头,比如 useMouseuseFetch。这不是 Vue 的强制规定,但社区都这么写,一眼就知道这是个组合式函数。

鼠标追踪例子

假设你想在多个组件里追踪鼠标位置。直接在组件里写的话,长这样:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

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))
</script>

<template>鼠标位置:{{ x }}, {{ y }}</template>

抽成组合式函数后,把”脏活”搬到外面:

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 }
}

组件里干净多了:

<script setup>
import { useMouse } from './useMouse.js'

const { x, y } = useMouse()
</script>

<template>鼠标位置:{{ x }}, {{ y }}</template>
Tip

每个调用 useMouse() 的组件都会创建自己独立的 xy,互不干扰。这就是组合式函数的隔离性。

嵌套调用——组合的力量

组合式函数可以互相调用,就像组件可以互相嵌套。比如,我们把”添加 DOM 事件监听器”的逻辑也抽出来:

import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback) {
  onMounted(() => target.addEventListener(event, callback))
  onUnmounted(() => target.removeEventListener(event, callback))
}

然后 useMouse 就能简化成:

import { ref } from 'vue'
import { useEventListener } from './useEventListener.js'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}

这就是”组合”的含义——小积木拼成大积木。

接受响应式输入

useMouse 不接受参数。但有些组合式函数需要跟着数据变化来重新执行——比如一个自动根据 URL 请求数据的组合式:

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 }
}

问题是:它只请求一次。想让 URL 变化时重新请求,需要让 url 变成响应式的。Vue 提供了 watchEffect + toValue 来处理:

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))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error }
}

现在你可以传三种形式进来:

// 1. 普通字符串——请求一次
const { data, error } = useFetch('/api/posts')

// 2. ref——url 变化时自动重新请求
const url = ref('/api/posts/1')
const { data, error } = useFetch(url)

// 3. getter 函数——追踪函数内部所有响应式依赖
const { data, error } = useFetch(() => `/api/posts/${props.id}`)
Note

toValue() 是 Vue 3.3 加入的。它把 ref 或 getter 归一成普通值。如果传的是 ref 就返回 .value,传函数就调用函数,传字符串就原样返回。

最佳实践

返回 ref 而不是 reactive

推荐组合式函数返回包含多个 ref 的普通对象:

// 推荐
return { x, y }

// 不推荐
return reactive({ x, y })

为什么?因为解构普通对象里的 ref 不会丢失响应式连接,但解构 reactive 对象会。

如果你非要用 reactive 包一层,也行:

const mouse = reactive(useMouse())
// mouse.x 仍然是响应式的

副作用清理

组合式函数里可以有副作用(添加监听器、请求数据),但要注意:

  1. SSR 场景下,DOM 相关的副作用放在 onMounted
  2. onUnmounted 里记得清理,防止内存泄漏

调用限制

组合式函数只能在 <script setup>setup()同步调用,否则 Vue 找不到当前组件实例来注册生命周期钩子。

Tip

<script setup> 里可以在 await 之后调用组合式函数,编译器会自动恢复上下文。其他地方不行。

组合式函数 vs Mixins

对比维度Mixins组合式函数
属性来源模糊,不清楚来自哪个 mixin清晰,解构就能看到
命名冲突容易冲突可以重命名变量解构
跨 mixin 通信隐式耦合通过参数显式传递

Mixins 是 Vue 2 的方案,Vue 3 不推荐继续用了。

本节回顾

  • 组合式函数是以 use 开头的普通函数,用 Composition API 封装有状态逻辑
  • 可以嵌套调用,像搭积木一样组合复杂逻辑
  • toValue() + watchEffect() 让组合式函数支持响应式输入
  • 返回 ref 普通对象,解构不会丢失响应式
  • 用于代码组织同样有效——把大组件按逻辑拆成多个组合式函数