一、简介
在开发中,你可能遇到过 API 调用杂乱无章、代码维护困难的问题。别担心,今天我们聊聊如何用 TypeScript 和 Axios 打造一个让后端开发也不得不点赞的 API 封装。无论你是初学者还是老手,这篇文章都会带你走上更优雅、更高效的开发之路!
二、了解后端如何编写 RESTful API1. 了解 RESTful API 的路径表示2. 了解后端如何编写的 RESTful API
接下来介绍 SpringBoot 和 Node 编写后端接口的大致案例 ( 注:比较简化 )
2.1 SpringBoot 编写的后端
@RestController
@RequestMapping("/users")
public class DemoController {
// 根据用户ID获取单个用户
@GetMapping("/{userId}")
public String getUserById(@PathVariable String userId) {
return "获取用户ID为 " + userId + " 的用户";
}
// 删除用户
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId) {
return "删除用户ID为 " + userId + " 的用户";
}
// 获取指定用户的所有帖子
@GetMapping("/{userId}/posts")
public String getUserPosts(@PathVariable String userId) {
return "获取用户ID为 " + userId + " 的所有帖子";
}
// 取消指定订单
@PostMapping("/orders/{orderId}/cancel")
public String cancelOrder(@PathVariable String orderId) {
return "取消订单ID为 " + orderId + " 的订单";
}
}
2.2 Node.js 编写的后端
const express = require('express');
const router = express.Router();
// 添加路径前缀 /users
router.use('/users', (req, res, next) => {
next();
});
// 根据用户ID获取单个用户
router.get('/:userId', (req, res) => {
const userId = req.params.userId;
res.send(`获取用户ID为 ${userId} 的用户`);
});
// 删除用户
router.delete('/:userId', (req, res) => {
const userId = req.params.userId;
res.send(`删除用户ID为 ${userId} 的用户`);
});
// 获取指定用户的所有帖子
router.get('/:userId/posts', (req, res) => {
const userId = req.params.userId;
res.send(`获取用户ID为 ${userId} 的所有帖子`);
});
// 取消指定订单
router.post('/orders/:orderId/cancel', (req, res) => {
const orderId = req.params.orderId;
res.send(`取消订单ID为 ${orderId} 的订单`);
});
module.exports = router;
2.3 分析接口相同点三、分析之前使用 axios 请求的优缺点1. 简单看一下之前在项目中 api 的使用
/** 接口描述 */
export function XXX(body: XXXType) {
return request<XXXResponse>('uri', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body
});
}
/** 接口描述 */
export function XXX(avatar: File) {
const formData = new FormData();
formData.append('avatar', avatar);
return request<XXXResponse>('uri', {
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formData
});
}
<template>
<div v-permission="['XXX:permission']"> </div>
</template>
<script lang="ts" setup>
// 引入后调用
XXX(body).then(({data}) => {
})
</script>
优点
快捷方便:直接使用request方法可以快速发起请求,代码简洁,开发速度快,适合简单的场景。
缺点
接口权限不清晰:由于权限信息没有明确绑定在接口上,导致在发起请求时难以清楚地了解所需权限,增加了开发和维护的难度。接口管理不便:路径和方法定义分散,修改接口路径或管理多个接口时比较麻烦,不利于代码的一致性和可维护性。不适合RESTful接口:在处理RESTful风格的接口时,代码结构不够清晰,容易混淆请求方法、路径和参数。模块信息不明确:无法通过代码直观地看到接口所在的模块或用途,导致项目的可读性和组织性较差。四、TypeScript 与 Axios 前端封装的实践
在进行 TypeScript 和 Axios 前端封装时,可以通过以下改进方法来解决之前所提到的问题。
问题分析:
权限绑定:可以通过定义接口类,将接口路径和权限信息绑定在一起,确保在使用接口时清晰了解所需的权限。路径剥离:将接口路径集中管理,避免路径和方法定义分散,提升代码的一致性和可维护性。模块划分:使用接口类将接口划分为不同模块,明确各接口的用途和所在模块,提高项目的可读性和组织性。1. 代码实践
改进的代码示例:
const API_BASE = '/user';
const API_SUFFIXES = {
/** 获取当前登录的用户信息 */
ME: '/me',
/** 分页获取用户数据 */
PAGE: '/page',
/** 获取表单数据 */
FORM: '/{userId}/form',
/** 新增用户 ( POST 请求 ) */
SAVE: '',
/** 删除用户 */
DELETE: '/{userIds}',
/** 修改用户 */
UPDATE: '/{userId}',
/** 修改用户状态 */
UPDATE_STATUS: '/{userId}/status',
/** 管理员修改用户密码 */
ADMIN_RESET_PASSWORD: '/{userId}/reset-password',
};
// 定义 USER_API 类
export class UserAPI {
// 其他接口省略...
/**
* 获取用户表单数据
* @param userId 用户Id
*/
static FORM = {
endpoint: (userId: string): string => {
return `${API_BASE}${API_SUFFIXES.FORM.replace("{userId}", userId)}`;
},
permission: "system:user:update",
request: (userId: string): AxiosPromise<UserForm> => {
return request<UserForm>({
url: UserAPI.FORM.endpoint(userId),
method: "get",
})
}
}
}
再看一个接口:
const API_BASE = '/user/profile';
const API_SUFFIXES = {
/** 获取个人信息 */
INFO: "",
/** 修改个人信息 */
UPDATE: "",
/** 绑定第三方账号 */
BIND_THIRD_PARTY: "/{type}/bind-third-party",
/** 解绑第三方账号 */
UNBIND_THIRD_PARTY: "/{oauthId}/unbind-third-party",
/** 修改密码 */
UPDATE_PASSWORD: "/password",
/** 修改用户头像 */
UPLOAD_AVATAR: "/avatar",
};
export class UserProfileAPI {
// 其他接口省略 ...
/**
* 修改个人信息
*/
static UPDATE = {
endpoint: `${API_BASE}${API_SUFFIXES.UPDATE}`,
request: (userProfileForm: UserProfileForm): AxiosPromise<void> => {
return request<void>({
url: UserProfileAPI.UPDATE.endpoint,
method: "put",
data: userProfileForm
})
}
}
/**
* 修改头像
*/
static UPLOAD_AVATAR = {
endpoint: `${API_BASE}${API_SUFFIXES.UPLOAD_AVATAR}`,
maxFileSize: 10 * 1024 * 1024, // 10M
allowedFileTypes: ['image/bmp', 'image/png', 'image/jpeg', 'image/gif'],
request: (avatar: File): AxiosPromise<string> => {
// 1. 创建一个FormData对象并附加文件
const formData = new FormData();
formData.append('avatar', avatar);
// 2. 请求更改头像
return request<string>({
url: UserProfileAPI.UPLOAD_AVATAR.endpoint,
method: "patch",
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
}
}
2. 封装后使用
当我们使用接口时
<template>
<div v-permission="[XXXAPI.SAVE.permission]"> </div>
</template>
<script lang="ts" setup>
// 引入后使用
XXXAPI.SAVE.request(body).then(({data}) => {
})
// 接口配置校验
XXXAPI.UPLOAD.maxFileSize
3. 分析优缺点
优点:
缺点:
五、源码
源码地址 | 在线演示 | 觉得不错可以给个start
前端源码位置 : yf/ yf-vue-admin / src / api
注意事项 :
六、结束语
每个人在设计代码时,都会根据自身的项目需求和经验采取不同的方案,正如莎士比亚所言:“一万个人心中有一万个哈姆雷特”。有人追求代码的简洁与快速开发,有人则更注重结构化与可扩展性。无论选择哪种方式,都没有绝对的对与错,只有最适合当前项目和团队的方案。希望通过这些分析与实践,能够为你提供新的思路,帮助你更好地权衡与选择适合自己的开发方式。