什么是前端API模块?
首先明确一个概念,本文说的前端API模块指的是什么?
在前端代码中(本文不包括BFF,BFF也划分到“后端”)存在请求后端服务接口代码,通常情况下,一个后端接口可能被多个页面使用,所以我们通常会把后端接口API相关的前端代码放在一个模块中统一管理,在需要发起请求的代码中,引用该模块,然后使用指定API的方法发起请求,从而达到复用代码、统一管理的目的,而这个管理API的模块,我将它命名为“前端API模块”。
因为我也不太清楚我们前端圈里怎么命名这东西,所以我姑且先这么称呼它吧。如果可以的话,在评论区告诉我这个应该怎么称呼更好?!
历史方案
首先说一下目前项目的方案,以及存在的问题。
方案:每个服务管理独立的API模块
我们将一个大分类的功能模块称之为服务,比如产品服务、用户服务等,然后前后端都使用服务分类将代码划分为不同的目录或者微服务。
后端的接口通过微服务提供,比如产品服务的API地址为 /api/products/v1/xxx,用户服务的API地址为 /api/users/v1/yyy 等。
前端类似地在每个服务目录下,提供单独的apis模块,比如在products目录下,有一个 apis.js 文件导出apis模块,该模块定义所有后端products服务的API地址常量。在页面代码中发起请求时,通过引入该模块,然后使用该模块导出的常量名称,然后使用请求方法发起请求。
// ./products/apis.js
const VERSION = 'v1';
const SERVICE = 'products';
const PRODUCT_LIST = `/api/${SERVICE}/${VERSION}/list`;
export default {
PRODUCT_LIST,
};
// ./products/pages/list.js
import APIS from '../apis';
import axios from 'axios';
const { PRODUCT_LIST } = APIS;
axios.get(PRODUCT_LIST, {
params: {
pageIndex: 1,
pageSize: 10,
}
}).then(() => {
// ...
}).catch(() => {
// ...
});
使用这种方案,当API接口地址发生变化时,只需要修改apis.js中的常量值即可。
问题
1、当多个服务使用相同的API时,需要修改多个服务的API,容易出现漏改情况;
2、当业务代码使用不规范时,没有按照规范引入API模块,修改API地址时,出现漏改情况;
3、业务代码还需要自行引入请求库,这些原本也可以内聚到API模块中实现;
改良方案
所以需要改良方案,解决历史方案存在的问题。
1、抽取到公共服务API模块或者独立的API服务
将所有的API都收起来,放在公共服务或者单独一个服务中进行管理,不再分散到每个服务中去。这样做的好处是,没有产品服务都使用同一个API服务,方便进行统一规范的管理,不管是API的调整,还是请求拦截,又或者是其他需要增强的功能,都只需要在一个API服务中进行变更,而不需要在每个产品服务中修改。这样,也就不会出现由于需要改动多处导致修改错误、漏改的问题。
当然,任何方案都不是完美的,方案的选择永远都是权衡的艺术。
这种方案的问题也会存在一些不尽人意的问题。
首先,它会导致公共资源加载成本和内存成本,会存在一定的冗余。公共资源加载时需要首先加载API模块代码,API模块导出的对象,需要长期占用内存空间。针对问题,我们可以通过按需加载的方式减少资源加载成本,以及利用缓存(sessionStorage等)将数据缓存下来,需要时再读取的方式减少内存成本。
其次,开发过程中增加了代码管理成本。当公共服务代码和产品服务代码分开代码库时,需要注意不同代码库的代码版本(通常指的是代码分支),否则可能会出现产品服务引用了历史版本的公共模块,而导致缺少相应API、或者API不争气的问题。
总的来说,新的方案是利大于弊的。
2、API模块提供更加完善的功能
之前的方案中,只是提供了API地址,功能很受限,所以新的方案中,我们不仅提供API地址,还需要把每个API作为一个完整的请求函数,提供默认的请求参数值,增强请求的容错性。
// ./apis/products.js
import axios from 'axios';
const VERSION = 'v1';
const SERVICE = 'products';
const PRODUCT_V1 = `/api/${SERVICE}/${VERSION}`;
// 获取产品列表接口
export function getProductList(params = {}) {
return axios.get(`${PRODUCT_V1}/list`, {
params: {
// 默认参数
pageIndex: 1,
pageSize: 10,
// 传入的参数
...params,
}
});
}
// 新增产品接口
export function addProduct(data) {
if(!data || !data.name) {
return Promise.reject('The [name] is required!');
}
return axios.post(`${PRODUCT_V1}`, {
data: {
...data,
name: data.name.trim(),
}
});
}
// 修改产品接口
export function addProduct(params, data) {
if(!params || !params.id) {
return Promise.reject('The [id] in params is required!');
}
if(data && data.name) {
return Promise.reject('The data.name is not allowed to be modified!');
}
return axios.put(`${PRODUCT_V1}/${params.id}`, {
data,
});
}
// ./apis/index.js
import axios from 'axios';
export * as products from './products';
// 拦截请求
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// 拦截响应
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
// ./products/pages/list.js
import { products } from './apis';
const { getProductList } = products;
getProductList()
.then(() => {
// ...
})
.catch(() => {
// ...
})
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!