上午好,今天为大家分享下个人对于前端API层架构的一点经验和看法。架构设计是一条永远走不完的路,没有最好,只有更好。这个道理适用于软件设计的各个场景,前端API层的设计也不例外,如果您觉得在调用接口时还存在诸多槽点,那就说明您的接口层架构还待优化。今天我以vue + axios为例,为大家梳理下我的一些经历和设想。
石器时代,痛苦直接调用axios,真的痛苦,每个调用的地方都要进行响应状态的判断,冗余代码超级多。
import axios from "axios" axios.get('/usercenter/user/page?pageNo=1&pageSize=10').then(res => { const data = res.data // 判断请求状态,success字段为true代表成功,视前后端约束而定 if (data.success) { // 结果成功后的业务代码 } else { // 结果失败后的业务代码 } })看起来确实很难受,每调用一次接口,就有这么多重复的工作!
青铜器时代,中规中矩为了解决直接调用axios的痛点,我们一般会利用Promise对axios二次封装,对接口响应状态进行集中判断,对外暴露get, post, put, delete等http方法。
axios二次封装 import axios from "axios" import router from "@/router" import { BASE_URL } from "@/router/base-url" import { errorMsg } from "@/utils/msg"; import { stringify } from "@/utils/helper"; // 创建axios实例 const v3api = axios.create({ baseURL: process.env.BASE_API, timeout: 10000 }); // axios实例默认配置 v3api.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'; v3api.defaults.transformRequest = data => { return stringify(data) } // 返回状态拦截,进行状态的集中判断 v3api.interceptors.response.use( response => { const res = response.data; if (res.success) { return Promise.resolve(res) } else { // 内部错误码处理 if (res.code === 1401) { errorMsg(res.message || '登录已过期,请重新登录!') router.replace({ path: `${BASE_URL}/login` }) } else { // 默认的错误提示 errorMsg(res.message || '网络异常,请稍后重试!') } return Promise.reject(res); } }, error => { if (/timeout\sof\s\d+ms\sexceeded/.test(error.message)) { // 超时 errorMsg('网络出了点问题,请稍后重试!') } if (error.response) { // http状态码判断 switch (error.response.status) { // http status handler case 404: errorMsg('请求的资源不存在!') break case 500: errorMsg('内部错误,请稍后重试!') break case 503: errorMsg('服务器正在维护,请稍等!') break } } return Promise.reject(error.response) } ) // 处理get请求 const get = (url, params, config = {}) => v3api.get(url, { ...config, params }) // 处理delete请求,为了防止和关键词delete冲突,方法名定义为deletes const deletes = (url, params, config = {}) => v3api.delete(url, { ...config, params }) // 处理post请求 const post = (url, params, config = {}) => v3api.post(url, params, config) // 处理put请求 const put = (url, params, config = {}) => v3api.put(url, params, config) export default { get, deletes, post, put } 调用者不再判断请求状态 import api from "@/api"; methods: { getUserPageData() { api.get('/usercenter/user/page?pageNo=1&pageSize=10').then(res => { // 状态已经集中判断了,这里直接写成功的逻辑 // 业务代码...... const result = res.result; }).catch(res => { // 失败的情况写在catch中 }) } } async/await改造使用语义化的异步函数
methods: { async getUserPageData() { try { const res = await api.get('/usercenter/user/page?pageNo=1&pageSize=10') // 业务代码...... const { result } = res; } catch(error) { // 失败的情况写在catch中 } } } 存在的问题语义化程度有限,调用接口还是需要查询接口url
前端api层难以维护,如后端接口发生改动,前端每处都需要大改。
如果UI组件的数据模型与后端接口要求的数据结构存在差异,每处调用接口前都需要进行数据处理,抹平差异,比如[1,2,3]转1,2,3这种(当然,这只是最简单的一个例子)。这样如果数据处理不慎,调用者出错几率太高!
难以满足特殊化场景,举个例子,一个查询的场景,后端要求,如果输入了搜索关键词keyword,必须调用/user/search接口,如果没有输入关键词,只能调用/user/page接口。如果每个调用者都要判断是不是输入了关键词,再决定调用哪个接口,你觉得出错几率有多大,用起来烦不烦?
产品说,这些场景需要优化,默认按创建时间降序排序。我擦,又一个个改一遍?
......
那么怎么解决这些问题呢?请耐心接着看......
铁器时代,it's cool我想到的方案是在底层封装和调用者之间再增加一层API适配层(适配层,取量身定制之意),在适配层做统一处理,包括参数处理,请求头处理,特殊化处理等,提炼出更语义化的方法,让调用者“傻瓜式”调用,不再为了查找接口url和处理数据结构这些重复的工作而烦恼,把ViewModel层绑定的数据模型直接丢给适配层统一处理。
对齐微服务架构