一、前言
之前接手了实习生写的项目,我对很多地方做了完善,其中包括 API 接口封装,因此借此机会将这部分经验转化为文字分享给大家。看前须知,项目是用 VueCLI 搭建的。
一般来说,公司的后端采用 RESTful API 的规范来编写接口的。
项目前期接口较少,因此前后端联调时,请求数据时一般会写成这样:
// 拿取货物数据 fetchGoodsData () { return axios.get('http://xxx.yyy.zzz:3000/api/product1/v1/goods') }
然而,随着产品的功能迭代,接口自然也会越来越多,如果还是按照上面的方式去写,将会面临一系列的麻烦:
第一、永久性变量冗余。 例如, http://xxx.yyy.zzz:3000/api/product1/v1/
,这一串几乎永远是冗余的;
第二、高强度心智负担。 例如,http://xxx.yyy.zzz:3000/api/product1/v1/carts?page=1&size=-1
,一旦传递的参数过多,就会出现漏写、写错的情况;
第三、模块过于扁平化。 我们从上面几个接口上是无法直接判断当前接口请求的数据究竟属于哪一个功能模块,而且请求含义不明确,非常依赖开发人员编写的方法名。
为了解决以上几个难题,我们就需要对 API 进行封装。
二、问题分析
面对这样一个复杂的问题,思路往往是反其道而行之的,“分而治之”, 让其简单化。
打开后端给的 API 文档,举个例子,假设有以下几个模块:
- 用户模块
- 货物模块
- 购物车模块
- ……
回想前言中提到的第三个问题——模块过于扁平化,无法区分接口属于哪个功能模块,无法区分请求含义。
如何让我们在发请求时更清晰地分辨接口呢?
// 拿取货物数据 fetchGoodsData () { return this.$api.goods.fetchGoodsData() }
如上图,只是观察函数调用即可明确功能模块、请求的具体含义。
与此同时还解决了第一个问题——永久性冗余,冗余的信息被封装到了 axios 实例中。
对于第二个问题——请求参数过多,认知负担增加,我们把需要传递的数据传进去即可,像这样:
// 拿取货物数据 fetchCartsData (params) { return this.$api.carts.fetchCartsData(params) } // 获取所有购物车数据,不分页 fetchCartsData({ pages: 1, size: -1 }) .then(data => { // ... }) .catch(e => console.error(e))
看到这里,如果你是一位新手,应该会一头雾水,那就对了。因为也许你并不知道http://xxx.yyy.zzz:3000/api/product1/v1/
去哪了,也不知道 ?page=1&size=-1
怎么就突然作为参数传到封装好的 API 函数里去了。
Take it easy,just go on.
三、Axios
我们简单过一下 Axios —— 基于 Promise 的 HTTP 客户端,可用于浏览器和 node.js,还可以拦截请求和响应,转化请求和响应数据,取消请求,自动转换 JSON 数据,防御 XSRF 攻击……
简而言之,它可以帮你获取数据,请求和响应时帮你拦截数据,总是返回一个 Promise,然后你从 Promise 里拿到数据,整理后展示到页面上。
回到第二节末尾提出的问题: http://xxx.yyy.zzz:3000/api/product1/v1/
去哪了??page=1&size=-1
去哪了?它们都被放进了 axios 实例中。具体来说,前者被放到了 baseURL 中,后者以对象的形式被放到了 params 中。参考链接:axios-http.com/docs/req_co…
如下图,一个 axios 实例主要由两个部分组成:拦截器、配置。
在正常的开发中,前端可能需要连接不同环境进行接口联调,因此其中,http://xxx.yyy.zzz:3000/api/product1/v1/
作为 baseURL 就不能写死,需要根据本地服务器的模式来进行转换。
打个比方,如果是开发环境,baseURL 切换为 http://192.168.1.1:30000
;如果是测试环境,baseURL 切换为 http://192.168.1.3:40000
。因此,我们只要将 baseURL 的地址写在环境变量中就可以了。
现在我们需要设置模式及其环境变量,之后通过 process.env 就可以获取到对应变量。
四、模式与环境变量
VueCLI 提供了几种默认的模式,但是一般我们都会自己自定义。
通过 --mode
可以定义模式,例如,我要定义测试模式(其他模式以此类推)。
打开 package.json,找到 scripts 这一项,添加测试模式:
"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test": "vue-cli-service serve --mode test" }
另外,我们需要在根目录下(和 src 目录同级)新建一个 .env.test
文件,用来保存环境变量:
NODE_ENV = 'test' VUE_APP_BASE_URL = 'http://192.168.1.3:4000'
之后,来到 main.js 中,添加一条 console.log(process.env);
。
打开控制台,执行命令:npm run test
项目运行后,在浏览器的控制台中就能看到 process.env 里保存着 NODE_ENV 和 VUE_APP_BASE_URL。
有了这两个变量,模式和环境变量就有了对应关系。然后将 process.env.VUE_APP_BASE_URL 赋值给 axios 实例的 baseURL,就能实现 baseURL 的灵活转换。
补充几点:
- mode 的参数与 .env 文件后缀有关系。
.env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入,但会被 git 忽略 .env.[mode] # 只在指定的模式中被载入 .env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
- VUE_APP_* 是 VueCLI 通过
webpack.DefinePlugin
静态地嵌入到客户端侧 的代码中的。 - 更多的疑惑,参考这篇文章:cli.vuejs.org/zh/guide/mo…
五、Axios 封装
Axios 的封装需要管理两件事情:一是创建实例,二是设置拦截器。
找到 utils 目录,在里面新建 request.js
5.1 创建实例
import axios from 'axios' // create an axios instance: <https://axios-http.com/docs/instance> const service = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 10 * 1000 }) export default service
可以看到这里的 baseURL 用的是就是对应模式下的环境变量了。
5.2 请求拦截器 & 响应拦截器
// ... // request interceptor: <https://axios-http.com/docs/interceptors> service.interceptors.request.use( config => { return config }, error => { return Promise.reject(error) } ) service.interceptors.response.use( response => { return response }, error => { return Promise.reject(error) } ) // ...
到此,axios 实例就准备完毕了,有 baseURL,有拦截器。
axios 的封装与本文主题无关,所以写得很简单。但是这里也给大家提供一篇参考。这是另外一种思维方式,也值得学习:developpaper.com/encapsulati…