Vue 3 中组合式函数 Composition Function 封装逻辑

FreeGuideOnline 最新 2026-07-06

什么是组合式函数

组合式函数(Composables)是 Vue 3 中利用组合式 API 封装和复用有状态逻辑的核心模式。与 React 的自定义 Hooks 类似,它允许开发者将组件中与响应式状态相关的逻辑提取到独立的函数中,从而让代码更清晰、更易于测试和跨组件共享。

一个组合式函数就是一个普通的 JavaScript 函数,但约定其名称以 use 开头。它内部可以使用 Vue 的响应式 API(如 refreactivecomputedwatch 等),并返回需要暴露给组件的状态或方法。

为什么需要封装逻辑

在没有组合式函数之前,逻辑复用主要依赖 Mixins 和作用域插槽。Mixins 存在来源不清晰命名冲突隐式依赖等问题。组合式函数通过显式引入和清晰的返回值,完美解决了这些痛点:

  • 透明性:所有使用的数据和函数都来自明确的导入和返回,没有“魔法”属性。
  • 灵活性:可以自由组合多个组合式函数,而且它们之间可以直接通信。
  • 可测试性:独立函数可以轻松进行单元测试,无需挂载整个组件。

从零开始创建组合式函数

让我们通过一个典型的鼠标跟踪示例来学习如何封装。

步骤一:编写基础逻辑

假设直接在组件中实现跟踪鼠标位置:

import { ref, onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    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 }
  }
}

步骤二:提取为组合式函数

创建一个新文件 useMouse.js,将上述逻辑移入一个以 use 开头的导出函数中。

// composables/useMouse.js
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 }
}

现在任何组件都可以简洁地复用它:

import { useMouse } from './composables/useMouse'

export default {
  setup() {
    const { x, y } = useMouse()
    return { x, y }
  }
}

常用组合式函数封装实例

封装 API 请求逻辑(useFetch)

网络请求是前端最常重复的逻辑。我们可以封装一个通用数据获取器,管理加载状态、错误和响应数据。

import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  const fetchData = async () => {
    loading.value = true
    error.value = null
    try {
      // toValue 支持 url 可能是 ref 或普通字符串
      const response = await fetch(toValue(url))
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  // 当 url 变化时自动重新请求
  watchEffect(() => {
    fetchData()
  })

  return { data, error, loading, refetch: fetchData }
}

使用方式:

import { ref } from 'vue'
import { useFetch } from './useFetch'

const url = ref('https://api.example.com/users')
const { data, loading, error } = useFetch(url)

封装本地存储逻辑(useLocalStorage)

把响应式状态同步到 localStorage,并保持类型支持。

import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const stored = localStorage.getItem(key)
  const value = ref(stored ? JSON.parse(stored) : defaultValue)

  // 在值改变时写入 localStorage
  watch(value, (newVal) => {
    localStorage.setItem(key, JSON.stringify(newVal))
  }, { deep: true })

  return value
}

组件中直接使用,无需关心持久化细节:

const theme = useLocalStorage('theme', 'light')
// theme 变化自动保存,页面刷新后仍保留上次的值

封装事件监听器清理(useEventListener)

避免手动重复编写 addEventListenerremoveEventListener

import { onMounted, onUnmounted } from 'vue'

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

使用:

useEventListener(window, 'resize', () => {
  console.log('窗口大小改变了')
})

组合式函数的最佳实践

约定命名方式

  • 函数名必须以 use 开头,遵循驼峰命名法,如 useDarkModeuseCounter
  • 返回值通常使用对象解构,以便按需引入,也保留了未来扩展的可能性。

保持无副作用输入

如果一个组合式函数接受参数,尽量让参数可以是 ref 或原始值,内部使用 toReftoValue 处理,使接口更加灵活。

import { toValue } from 'vue'

export function useTitle(newTitle) {
  const title = toValue(newTitle)
  document.title = title
}

返回清晰的接口

只返回组件真正需要的状态和方法。避免返回整个内部对象,利用 readonly 保护不需要被外部修改的状态。

import { readonly } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return {
    count: readonly(count),  // 外部只读
    increment
  }
}

组合函数中可使用生命周期

组合式函数内部可以自由使用 onMountedonUnmountedwatch 等。当组件使用该函数时,这些生命周期钩子会自动与组件的生命周期绑定,无需额外处理。

避免过度封装

不要为了封装而封装。如果逻辑与组件强耦合且没有复用需求,留在组件内可能更简单。组合式函数最适合复用多个组件共享的状态+行为

setup 中使用多个组合式函数

组合式函数的强大之处在于可以自由组合,甚至可以相互调用。

import { useMouse } from './useMouse'
import { useFetch } from './useFetch'

export default {
  setup() {
    const { x, y } = useMouse()
    const { data: user } = useFetch(`/api/user?x=${x}&y=${y}`)

    return { x, y, user }
  }
}

总结

组合式函数是 Vue 3 生态中代码组织和复用的最佳范式。它职责单一、可组合、易测试,真正实现了“将逻辑作为乐高积木”进行搭建。通过掌握组合式函数的封装技巧,你可以大幅提升代码的可读性可维护性开发效率

立刻尝试将你项目中的重复逻辑封装成 useXXX 吧!