在 Vue 3 项目中,setup 函数里调用 API,真正的挑战往往不在于发起请求本身,而在于如何将这套逻辑组织得清晰、可复用,并且不污染组件的核心业务逻辑。直接在 setup 里写 axios.get() 虽然快捷,但很快就会让你陷入重复处理 loading 状态、错误捕获、缓存和请求取消的泥潭

在 Vue 3 项目中,setup 函数里调用 API,真正的挑战往往不在于发起请求本身,而在于如何将这套逻辑组织得清晰、可复用,并且不污染组件的核心业务逻辑。直接在 setup 里写 axios.get() 虽然快捷,但很快就会让你陷入重复处理 loading 状态、错误捕获、缓存和请求取消的泥潭。要跳出这个循环,组合式函数(Composable)的合理封装,才是通往优雅代码的必经之路。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
一个设计精良的 API 组合式函数,其职责边界应该非常清晰:它只关心如何发起请求、如何处理响应结构,并暴露一个可控的执行接口。它不应该越界去直接修改组件内的 ref,也不该主动触发 UI 更新。
那么,一个理想的封装应该长什么样?
{ data, error, loading, execute }。这套结构语义清晰,调用方可以按需取用,非常直观。execute 是函数,而非自动执行:这意味着控制权交给了组件。数据是在按钮点击后拉取,还是在路由就绪后加载,完全由调用方决定。ref 包裹:错误和加载状态被封装为响应式引用,这样在模板中就能直接通过 v-if="loading" 进行绑定,实现 UI 的自动响应。理论说再多,不如看代码。下面是一个生产环境可用的、最小化的封装示例(以 axios 为例,并支持 TypeScript):
import { ref, Ref } from 'vue'
import axios from 'axios'
interface UseApiOptions {
manual: boolean // 是否手动触发,默认 false(自动执行)
}
export function useApi(url: string, options: UseApiOptions = {}) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
const execute = async () => {
loading.value = true
error.value = null
try {
const res = await axios.get(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('/api/user/1')
const handleRefresh = () => execute()
return () => (
{loading.value '加载中...' : null}
{error.value `错误:${error.value.message}` : null}
{data.value {data.value.name}
: null}
)
}
})
当然,真实业务场景远比基础示例复杂。以下几个进阶能力,你很快就会用到。关键在于,这些逻辑都应该沉淀在组合式函数内部,而不是散落在各个组件里。
url 改为一个函数,例如 (id: string) => `/api/user/${id}`。让 execute 方法接收参数,并在每次执行前重置相关状态。AxiosController 或原生的 AbortController。在每次 execute 调用时生成一个新的 controller,并在下一次调用前取消上一次的请求。这对于搜索输入框等场景至关重要。url + JSON.stringify(params) 作为键来缓存响应结果。命中缓存时直接返回,能有效避免不必要的重复请求。execute 调用(比如实时搜索),可以使用 lodash.debounce 等工具对执行函数进行包装,返回一个新的防抖函数。封装的目的在于简化使用,而非隐藏必要的复杂性。有几个常见的反模式需要警惕:
useRequest 的巨型函数里。这会导致参数列表不断膨胀,类型推导困难,最终让维护成本飙升。notification.success() 或 router.push()。这违反了单一职责原则,会让组件失去对副作用和流程的控制权。data,而忽略了 res.headers、res.status 等信息。在处理分页、鉴权等场景时,这些信息往往是必需的。any 或 unknown 敷衍了事。充分利用 TypeScript 的类型推导,这是组合式函数封装带来的核心优势之一。说到底,一个好的组合式函数应该像一块乐高积木:功能纯粹且独立,但又能和其他积木完美拼接,共同构建出复杂的应用。它不替你做所有决定,只是为你提供一个稳固、可靠的“把手”。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述