如何在 setup 中优雅地调用 API:组合式函数封装异步请求的干货教程

作者:袖梨 2026-06-07
Vue 3 中调用 API 的核心是使用组合式函数封装请求逻辑,聚焦发起请求、处理响应、暴露 execute 接口,返回 { data, error, loading, execute },支持手动触发、错误加载状态响应式绑定及进阶能力如参数化、取消、缓存与防抖。

在 Vue 3 的 setup 中调用 API,核心不是“怎么发请求”,而是“如何组织得清晰、可复用、易测试、不污染组件逻辑”。直接在 setup 里写 axios.get() 看似快,但很快会陷入重复处理 loading、error、缓存、取消请求的泥潭。真正的优雅,来自组合式函数(Composable)的合理封装。

明确职责:组合式函数只管“请求逻辑”,不管“UI 状态”

一个高质量的 API 组合式函数,应聚焦三件事:发起请求、处理响应结构、暴露可控的执行接口。它不该直接修改组件内的 ref,也不该主动触发 UI 更新。

  • 返回值推荐解构对象:{ data, error, loading, execute },语义清晰,按需使用
  • execute 是函数,而非自动执行——让调用方决定何时拉数据(比如点击后、路由就绪后)
  • 错误和加载状态用 ref 包裹,方便在模板中响应式绑定(v-if="loading"

基础封装:带 loading 和 error 处理的通用模式

以下是一个生产可用的最小可行封装(以 axios 为例):

(复制即用,支持 TypeScript)
import { ref, Ref } from 'vue'import axios from 'axios'interface UseApiOptions {  manual?: boolean // 是否手动触发,默认 false(自动执行)}export function useApi<T>(url: string, options: UseApiOptions = {}) {  const data = ref<T | null>(null)  const error = ref<Error | null>(null)  const loading = ref(false)  const execute = async () => {    loading.value = true    error.value = null    try {      const res = await axios.get<T>(url)      data.value = res.data      return res.data    } catch (e) {      error.value = e as Error      throw e    } finally {      loading.value = false    }  }  if (!options.manual) {    execute()  }  return {    data,    error,    loading,    execute  }}

在组件中使用时干净利落:

import { useApi } from '@/composables/useApi'export default defineComponent({  setup() {    const { data, loading, error, execute } = useApi<User>('/api/user/1')    const handleRefresh = () => execute()    return () => (      <div>        {loading.value ? '加载中...' : null}        {error.value ? `错误:${error.value.message}` : null}        {data.value ? <h2>{data.value.name}</h2> : null}        <button onClick={handleRefresh}>刷新</button>      </div>    )  }})

进阶技巧:支持参数、取消请求、缓存与防抖

真实业务中,你很快会需要这些能力。它们不该堆在组件里,而应沉淀到组合式函数中:

  • 动态参数:把 url 改为函数,如 (id: string) => `/api/user/${id}`execute 接收参数并重置状态
  • 请求取消:用 AxiosControllerAbortController,在 execute 中生成新 controller,并在下一次调用前 abort 上次(尤其适合搜索输入框)
  • 简单内存缓存:用 Map 缓存 url + JSON.stringify(params) 对应的结果,命中则直接 resolve,避免重复请求
  • 节流/防抖:对高频触发的 execute(如搜索),用 lodash.debounce 包一层,返回新的防抖函数

避坑提醒:别让组合式函数变成“黑盒”

封装是为了简化,不是隐藏复杂度。几个常见反模式要警惕:

  • 把所有 API 请求都塞进一个叫 useRequest 的万能函数里——参数越来越多,类型难推导,维护成本飙升
  • 在组合式函数里直接调用 notification.success()router.push() —— 违反关注点分离,组件失去控制权
  • 不暴露原始响应(如 res.headersres.status),导致分页、鉴权头等场景无法处理
  • 忽略 TypeScript 类型推导,用 anyunknown 敷衍,失去类型安全优势

好的组合式函数,应该像乐高积木:单个功能纯粹,拼起来却能构建复杂应用。它不替你做决定,只给你稳当的把手。

相关文章

精彩推荐