前端项目中如何更优雅的管理和使用后端接口

987 阅读2分钟

在写这篇文章的时候,我尝试去找一下有没有相似的插件来实现,但是似乎都没有人像我这样做。如果有,请分享给我。

在刚入行前端的时候,开始流行前后端分离,我刚入职新公司,发现大家都是这么对接的:

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 interfaceexport 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小程序也支持。