Vue3 Axios企业级封装终章:手把手实现useRequest Hook,自动管理请求状态
各位前端小伙伴,做Vue3项目时是不是总有这样的烦恼:明明已经封装了Axios,可每个组件里调接口,还是要反复写loading、error、data的状态管理,手动处理请求触发,甚至参数变了还要重新写请求逻辑?代码冗余不说,维护起来更是头大。
其实解决这个问题的核心,就是封装一个通用的useRequest Hook——把网络请求的通用逻辑全抽离,组件里一行代码就能搞定请求加状态管理。这篇文章作为Vue3 Axios企业级封装系列的终章,借鉴React ahooks中useRequest的设计理念,手把手实现一个适配Vue3的简化版useRequest,自动管理状态、支持依赖追踪、可手动触发,企业级项目直接拎走用。
在此之前,我们已经完成了Axios架构设计、Mock服务搭建、请求取消/防重/重试等核心功能,这套封装组合起来,能直接覆盖日常开发90%的网络请求需求。
一、为什么一定要封装Vue3的useRequest
网络请求的核心痛点,从来都不是调接口本身,而是请求状态的重复管理:
每个请求都要定义loading(控制加载态)、error(捕获错误)、data(接收返回值),代码复制粘贴到吐
组件挂载时要手动调请求,表单提交要单独写触发逻辑,逻辑分散
当请求参数(如ID、页码)变化时,要手动监听并重新发起请求,多一步操作就多一分bug
而useRequest的核心价值,就是把这些通用逻辑抽离成独立的Hook,组件中只需一行代码引入,就能获取请求的所有状态和触发方法,彻底告别重复代码,让组件只专注于渲染逻辑。这也是Vue3组合式API的核心设计思想。
二、useRequest核心功能,一次满足所有需求
我们要实现的useRequest是简化版但够用版,核心实现4个企业级必备功能,轻量且实用:
自动状态管理:内置loading/error/data响应式状态,直接解构使用,无需手动定义
自动请求:组件挂载时可自动发起请求,无需手动调用
手动触发:提供run方法,支持表单提交等手动触发请求的场景
依赖追踪:监听响应式依赖变化,自动重新发起请求,适配参数动态变化的场景
三、手把手实现useRequest Hook,TS版可直接复用
整个实现过程分5步,基于TypeScript开发,类型提示拉满,目录结构遵循Vue3项目的企业级规范,直接复制到项目中就能用。
3.1 目录结构与文件创建
在项目的src/http/core目录下创建use-request.ts文件,作为useRequest Hook的核心实现文件;同时在src/http/core/index.ts中统一导出,方便组件引入。
src/
└── http/
└── core/
├── use-request.ts // useRequest核心实现
└── index.ts // 统一导出入口3.2 定义配置项类型(UseRequestOptions)
首先定义useRequest的入参配置项类型,包含自动请求、依赖项、初始数据、各类回调函数:
import type { WatchSource } from 'vue'
import type { AxiosRequestConfig } from 'axios'
/**
* useRequest配置项类型
* @template T 请求返回数据的类型
*/
export interface UseRequestOptions<T> {
// 是否在组件挂载时自动请求,默认true
auto?: boolean
// 响应式依赖项,变化时自动重新请求
deps?: WatchSource<any>[]
// 请求的初始数据
initialData?: T
// 请求前的回调函数
onBefore?: () => void
// 请求成功的回调函数,接收返回数据
onSuccess?: (data: T) => void
// 请求失败的回调函数,接收错误信息
onError?: (error: any) => void
// 请求完成的回调函数(无论成功/失败都会执行)
onFinally?: () => void
}3.3 定义返回结果类型(UseRequestReturn)
再定义useRequest的返回值类型,包含响应式状态和手动触发方法:
import type { Ref } from 'vue'
import type { AxiosRequestConfig } from 'axios'
/**
* useRequest返回结果类型
* @template T 请求返回数据的类型
*/
export interface UseRequestReturn<T> {
// 请求返回的响应式数据
data: Ref<T | undefined>
// 请求加载状态,true表示加载中
loading: Ref<boolean>
// 请求错误信息,null表示无错误
error: Ref<any | null>
// 手动触发请求的方法,可传入Axios配置项
run: (config?: AxiosRequestConfig) => Promise<T>
}3.4 核心函数实现(useRequest)
这是最关键的一步,实现useRequest的核心逻辑,结合Vue3的ref和watch API,处理请求执行、状态更新、依赖监听:
import { ref, watch } from 'vue'
import type { AxiosRequestConfig } from 'axios'
import type { UseRequestOptions, UseRequestReturn } from './type'
/**
* Vue3通用网络请求Hook
* @template T 请求返回数据的类型
* @param requestFn 异步请求函数,需返回Promise
* @param options 配置项,默认空对象
* @returns 包含请求状态和方法的对象
*/
export function useRequest<T>(
requestFn: (config?: AxiosRequestConfig) => Promise<T>,
options: UseRequestOptions<T> = {}
): UseRequestReturn<T> {
const {
auto = true,
deps = [],
initialData,
onBefore,
onSuccess,
onError,
onFinally
} = options
const data = ref<T | undefined>(initialData)
const loading = ref(false)
const error = ref<any | null>(null)
const executeRequest = async (config?: AxiosRequestConfig): Promise<T> => {
try {
onBefore?.()
loading.value = true
error.value = null
const result = await requestFn(config)
data.value = result
onSuccess?.(result)
return result
} catch (err) {
error.value = err
onError?.(err)
throw err
} finally {
loading.value = false
onFinally?.()
}
}
const run = (config?: AxiosRequestConfig): Promise<T> => {
return executeRequest(config)
}
if (auto) {
executeRequest()
}
if (deps.length > 0) {
watch(deps, () => {
executeRequest()
}, { deep: true })
}
return {
data,
loading,
error,
run
}
}3.5 统一导出Hook
在src/http/core/index.ts中导出useRequest,方便组件通过统一路径引入:
// 导出useRequest Hook及相关类型
export * from './use-request'四、三大实战场景,快速上手用起来
实现完useRequest后,结合列表查询、详情获取、表单提交三个最常见的业务场景,教你如何快速使用。
场景1:基本使用 —— 组件挂载自动请求(列表查询)
适用于商品列表、用户列表等组件挂载即查询的场景。
<template>
<div class="list-page">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="error" class="error">请求失败:{{ error.message }}</div>
<div v-else class="list">
<ul>
<li v-for="item in data?.list" :key="item.id">{{ item.title }}</li>
</ul>
<button @click="run" class="refresh-btn">刷新列表</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRequest } from '@/http/core'
import { demoService } from '@/services/demo-service'
import type { PageData, Demo } from '@/services/demo-service'
const { data, loading, error, run } = useRequest<PageData<Demo>>(() =>
demoService.getList({ pageNum: 1, pageSize: 10 })
)
</script>场景2:依赖追踪 —— 参数变化自动请求(详情页)
适用于商品详情、用户详情等参数动态变化的场景。
<template>
<div class="detail-page">
<h1>Demo详情</h1>
<select v-model="demoId">
<option value="1">Demo 1</option>
<option value="2">Demo 2</option>
<option value="3">Demo 3</option>
</select>
<div v-if="loading">加载中...</div>
<div v-else-if="error">请求失败:{{ error.message }}</div>
<div v-else class="detail">
<h2>{{ data?.title }}</h2>
<p>{{ data?.content }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRequest } from '@/http/core'
import { demoService } from '@/services/demo-service'
import type { Demo } from '@/services/demo-service'
const demoId = ref(1)
const demoIdRef = computed(() => demoId.value)
const { data, loading, error } = useRequest<Demo>(
() => demoService.getDetail(Number(demoId.value)),
{ deps: [demoIdRef], auto: true }
)
</script>场景3:手动触发 —— 禁用自动请求(表单提交)
适用于登录、注册、创建数据等表单提交场景。
<template>
<div class="create-page">
<h1>创建Demo</h1>
<input v-model="title" placeholder="请输入标题" class="input" />
<input v-model="content" placeholder="请输入内容" class="input" />
<button @click="handleCreate" :disabled="loading" class="submit-btn">
{{ loading ? '创建中...' : '立即创建' }}
</button>
<div v-if="error" class="error">创建失败:{{ error.message }}</div>
<div v-if="data" class="success">创建成功:{{ data.title }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRequest } from '@/http/core'
import { demoService } from '@/services/demo-service'
import type { Demo } from '@/services/demo-service'
const title = ref('')
const content = ref('')
const { data, loading, error, run } = useRequest<Demo>(
(params) => demoService.create(params),
{ auto: false }
)
const handleCreate = async () => {
if (!title.value || !content.value) return
await run({
title: title.value,
content: content.value,
author: 'test',
status: true,
createTime: new Date().getTime()
})
title.value = ''
content.value = ''
}
</script>五、封装总结与扩展方向
核心总结
useRequest的核心是抽离通用逻辑,将网络请求的状态管理、触发方式、依赖监听封装成独立Hook,让组件代码更简洁
最佳实践:将API调用逻辑封装到服务层(如demo-service.ts),useRequest只负责调用服务层方法,实现关注点分离,便于后续API维护
这套封装轻量且通用,基于Vue3组合式API和TypeScript开发,适配绝大多数企业级Vue3项目
扩展方向(按需实现)
本文实现的是简化版useRequest,大家可以根据项目需求,在此基础上扩展更多实用功能:
请求缓存:缓存请求结果,相同参数不再重复请求,提升性能
轮询请求:支持定时轮询,适用于实时数据刷新场景
防抖节流:防止重复点击提交,适配高频请求场景
取消请求:结合之前实现的Axios取消请求功能,支持手动取消正在进行的请求
空数据处理:内置空数据状态,统一处理空数据渲染
最后
这套Vue3 Axios封装 + useRequest Hook的组合,是我在多个企业级项目中亲测好用的方案,从基础的Axios封装到高阶的Hook实现,一步一步解决了网络请求的所有痛点。
Vue3的组合式API最大的魅力,就是让我们能自由地抽离和复用逻辑,告别Vue2选项式API的束缚。希望这篇文章能帮你简化项目中的网络请求代码,提升开发效率。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!