让后端开发赞不绝口的 API 封装技巧:用 Axios 实现高效前端请求管理

一、简介

在开发中,你可能遇到过 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

注意事项 :

六、结束语

每个人在设计代码时,都会根据自身的项目需求和经验采取不同的方案,正如莎士比亚所言:“一万个人心中有一万个哈姆雷特”。有人追求代码的简洁与快速开发,有人则更注重结构化与可扩展性。无论选择哪种方式,都没有绝对的对与错,只有最适合当前项目和团队的方案。希望通过这些分析与实践,能够为你提供新的思路,帮助你更好地权衡与选择适合自己的开发方式。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享