在写这篇文章的时候,我尝试去找一下有没有相似的插件来实现,但是似乎都没有人像我这样做。如果有,请分享给我。
在刚入行前端的时候,开始流行前后端分离,我刚入职新公司,发现大家都是这么对接的:
this.$http.post('/api/ExchangeancelGift', JSON.stringify(postParams))
.then(res => {
this.$vux.alert.show({
content: res.err_msg,
onHide: () => {
this.hasBtn = false
}
})
})
我问前端组长说:如果其他页面也要用到这个接口怎么办,再写一次?
前端组长说,那我们以后这么写:
export const removeActivity = params => { return axios.post(`${base}/activity/remove`, { params: params }); };
export const editActivity = params => { return axios.post(`${base}/activity/edit`, { params: params }); };
export const getPlatform = params => { return axios.get(`${base}/platform/list`, { params: params }); };
之后在接手一些大企的二手代码的时候,发现也差不多是这样写:
// ab服务查询分页组
export function queryBusinessGroupApi(data) {
const url = '/techPlatform/ab/listQueryBusinessGroup';
return request({
url: url,
'Content-type': 'application/json',
method: 'post',
data
});
}
// 删除ab服务分组
export function delBusinessGroupApi(data) {
const url = '/techPlatform/ab/delBusinessGroup';
return request({
url: url,
'Content-type': 'application/json',
method: 'post',
data
});
}
但是发现,光是给每个API命名就很头疼,每次都要import。这么多接口写在一个文件里面,密密麻麻很难阅读。后期也不知道这些接口的入参要求,返回值是什么。多人协作开发还可能冲突。
于是乎我提出了,一个api一个文件的做法。它是长这样子的:
// src/service/apis/antifake.js
/**
* 接口描述
*/
export default function(ctx) {
this.$http({
method: 'post',
url: '/api/antifake',
params: ctx.params,
data: ctx.data,
loading: true,
}).then(e => {
if (e.code === '01') {
return ctx.success(e.body)
}
ctx.fail(e.message)
})
}
再利用require.context特性,简单写一个自动读取接口文件的方法:
// src/service/apis.js
/* 此文件会自动生成,请勿修改 */
import axios from './axios.config'
const request = function(api) {
return ctx => {
ctx = {
success() {},
fail() {},
error() {},
...ctx,
}
return api.bind(this)(ctx)
}
}
// 自动注册/src/service/apis的所以接口
const apis = {}
const allApis = require.context('./apis', true, /\.js$/)
allApis.keys().forEach(key => {
const path = key
.match(/\.\/(.+?)\.js/)[1]
.replace(/\//g, '.')
.split('.')
if (path.length === 1) {
apis[path[0]] = request.bind(apis)(allApis(key).default)
}
if (path.length === 2) {
apis[path[0]] = {}
apis[path[0]][path[1]] = request.bind(apis)(allApis(key).default)
}
})
apis.$http = axios
apis.$api = apis
// 可以在此处注册一些弹框组件
export default apis
这样一来,分散在各个文件中的api都会被自动集中到apis里面,然后将其全局注册在vue那里(早期Vue2):
Vue.prototype.$api = apis
之后在vue2里面就可以直接用this.$api.antifake这样去访问,解决了每次都要import的问题。
但是这样并没有解决入参、返回值和接口提示问题。
时间来到了Vue3+Typescript时代,上面提到的问题得到了解决。
现在api文件,它是长这样的:
// src/service/apis/user/list.ts
import type { AxiosRequestConfig } from 'axios'
import http from '@/service/axios.config'
interface IRequestConfig extends AxiosRequestConfig {
params: {
id: number
name: string
}
data: {
id: number
name: string
}
}
export interface UserInfo {
id: number
name?: string
age?: number
}
interface IResponseData {
code: string
message: string
body: UserInfo[]
}
/**
* @description 用户列表API
*/
export default function api(ctx: IRequestConfig): Promise<IResponseData> {
return http({
method: 'get',
url: '/api/user/list',
...ctx,
})
}
它可以很好的利用typescript去做一些入参和返回值的声明。然后再给apis写一份声明。大致如下:
import UserList from '@/service/apis/user/list'
export interface IApis {
user: {
list: typeof UserList,
};
}
declare module '@vue/runtime-core' {
declare interface ComponentCustomProperties {
$api: IApis;
}
}
这样一来,每次使用this.$api.xxx的时候都会有提示,会提示你有哪些接口,入参要求,返回值有哪些。
对于<script setup>同样是支持的,只不过由于没有全局概念,需要每次import一下apis就行了。
聪明如你以及懒人的我肯定就会说了,每次都要写这份声明,也很麻烦啊。
于是乎我写了个vite插件:vite-plugin-normalizing-apis。
它能解决什么问题呢?
- 它能自动读取指定接口目录(比如:src/service/apis),把所有接口集中在一个虚拟模块;
- 它能在
dev模式下实时热更新虚拟模块; - 它能自动生成接口的
types,并且放在指定目录(比如:src/service/types.d.ts); - 它会生成一个
namespace Apis,关联每个接口的export interface、export type; - 它能监听接口目录,如果创建了一个空的ts文件,它会写入一个指定的模板。
是它,是它,就是它,有了它,我们就可以专注对每个接口进行单独维护,包括每个接口的返回值声明(命名重复也不怕了)。
具体的用法可以看这里README.md。
也有一个examples可以参考一下。
我说一些使用技巧:
1、生成的types.d.ts文件最好忽略掉不要提交到git,避免冲突。
2、接口可以按功能用目录分类。
比如你的页面是src/pages/user/list.vue。
那么你的接口可以是:
src/service/apis/user/list/create.ts、src/service/apis/user/list/update.ts、src/service/apis/user/list/delete.ts
3、每个接口返回值的声明,你可以在命名空间Apis拿到(前提是要export出来):
<script setup lang="ts">
import { Apis, apis } from '@/service'
const userInfo = ref<Apis.user.list.UserInfo[]>([])
async function getUserList() {
const res = await apis.user.list({
params: {
id: 1,
name: 'test'
},
data: {
id: 1,
name: 'test'
}
})
userInfo.value = res.body
}
</script>
4、如果你用一些auto-import插件,就可以省了import { Apis, apis } from '@/service'这一步导入。
对了,这个插件支持所有vite@4+项目,实测uniapp小程序也支持。