Vue3如何避免API调用代码重复?实现声明式类型安全API服务
Vue3 API调用优化:实现声明式类型安全链式调用方案
问题背景
当前Vue3项目采用的API调用流程存在高重复度问题:
- 在
api文件夹中为每个实体的CRUD编写独立函数,子资源(如练习、选项)需重复传入courseId等参数,代码结构高度雷同; - 通过Vuex Action调用API函数并将数据存入Store;
- 组件通过访问Store状态、分发Action获取数据。
期望实现声明式定义路由与参数的类型安全链式调用,示例如下:
getApiService().courses(courseId).delete() // 删除课程 getApiService().courses(courseId).exercises().get(exerciseId) // 获取单个练习 getApiService().courses(courseId).exercises().post(payload) // 创建练习并返回结果 getApiService().courses(courseId).exercises().get(exerciseId).choices().list() // 获取练习的选项列表
解决方案
一、手动实现思路(类型安全链式API)
核心通过嵌套类结构+TypeScript泛型维护路由路径、参数类型与返回值类型,结合axios封装基础请求逻辑。
1. 封装基础API服务类
负责维护当前路径,提供通用CRUD方法,支持嵌套子资源:
import axios from 'axios'; // 基础API服务类,泛型T指定当前资源的返回类型 class ApiService<T> { private path: string; constructor(path: string = '') { this.path = path; } // 拼接子资源路径,返回新的ApiService实例 protected resource<U>(name: string): ApiService<U> { return new ApiService<U>(`${this.path}/${name}`); } // 拼接带参数的资源路径(如/courses/:courseId) protected resourceWithParam<U>(name: string, param: string | number): ApiService<U> { return new ApiService<U>(`${this.path}/${name}/${param}`); } // 获取资源列表 async list(): Promise<T[]> { const res = await axios.get(this.path); return res.data; } // 获取单个资源 async get(id: string | number): Promise<T> { const res = await axios.get(`${this.path}/${id}`); return res.data; } // 创建资源 async post(payload: Partial<T>): Promise<T> { const res = await axios.post(this.path, payload); return res.data; } // 更新资源 async put(id: string | number, payload: Partial<T>): Promise<T> { const res = await axios.put(`${this.path}/${id}`, payload); return res.data; } // 删除资源(支持删除单个或当前路径指向的资源) async delete(id?: string | number): Promise<void> { const targetPath = id ? `${this.path}/${id}` : this.path; await axios.delete(targetPath); } }
2. 定义业务嵌套资源类
继承基础类,声明子资源方法并指定类型:
import { Course, Exercise, Choice } from '@/types'; // 选项资源API class ChoiceApi extends ApiService<Choice> {} // 练习资源API,嵌套选项子资源 class ExerciseApi extends ApiService<Exercise> { choices(): ChoiceApi { return this.resource<Choice>('choices'); } } // 课程资源API,嵌套练习子资源 class CourseApi extends ApiService<Course> { // 静态方法:生成带courseId的课程路径实例 static courses(courseId: string | number): CourseApi { return new CourseApi(`/courses/${courseId}`); } exercises(): ExerciseApi { return this.resource<Exercise>('exercises'); } } // 顶层API入口 export function getApiService() { return { courses: CourseApi.courses }; }
3. 实际调用示例
完全符合需求的类型安全链式调用:
// 删除指定课程 await getApiService().courses('course_123').delete(); // 获取指定课程下的单个练习 const exercise = await getApiService().courses('course_123').exercises().get('ex_456'); // 创建课程下的练习 const newExercise = await getApiService().courses('course_123').exercises().post({ title: '新练习', content: '练习内容' }); // 获取指定练习的选项列表 const choices = await getApiService().courses('course_123').exercises().get('ex_456').choices().list();
4. 结合Vuex的优化
在Vuex Action中直接调用链式API,减少重复代码:
// store/modules/courses.ts import { getApiService } from '@/api/service'; const actions = { async fetchCourse({ commit }, courseId: string) { const course = await getApiService().courses(courseId).get(); commit('SET_COURSE', course); }, async createExercise({ commit }, { courseId, payload }) { const exercise = await getApiService().courses(courseId).exercises().post(payload); commit('ADD_EXERCISE', exercise); } };
二、适用于Vue3的现有工具包
如果不想手动实现,可选择以下成熟工具:
- Vue Query:作为数据获取缓存库,可与自定义链式API结合,替代Vuex处理缓存、自动重新请求等逻辑,减少Store冗余代码;
- ts-rest:基于TypeScript的声明式API工具,通过Schema定义接口,自动生成类型安全的客户端调用代码,天生支持嵌套资源与链式调用;
- OpenAPI Generator:若后端提供OpenAPI/Swagger文档,可直接生成TypeScript类型安全的API客户端,自动处理嵌套资源与参数,无需手动编写重复代码。
内容的提问来源于stack exchange,提问作者Samuele B.
相关产品推荐
相关产品推荐

