六、API 接口封装
最后一步,对于 API 接口封装,我们使用面向对象的方式组织数据。
6.1 文件结构划分
在 src 目录下新建 api 目录,然后根据后端 API 接口文档划分模块:
src/api // api interface files |_ user // 用户模块 |_ index.js |_ goods // 货物模块 |_ index.js |_ carts // 购物车模块 |_ index.js |_ index.js
6.2 按模块编写
以货物模块为例,路径为:src/api/goods/index.js
import axios from '@/utils/request' // axios 实例引入(第五节封装的) const goods = { // 1. 获取所有货物信息 getGoods: () => axios({ url: '/api/product1/v1/goods', method: 'get' }), // 2. 获取某条货物信息 getGoodsById: (id) => axios({ url: `/api/product1/v1/goods/${id}`, method: 'get' }), // 3. 新增一条货物信息 addGoods: (data) => axios({ url: '/api/product1/v1/goods', method: 'post', data }), // 4. 删除某条货物信息 deleteGoodsById: (id) => axios({ url: `/api/product1/v1/goods/${id}`, method: 'delete' }), // 5. 更新某条货物信息 // put 与 patch 的区别:put 所有字段均要传递;patch 可选字段传递,不用全部传 updateGoodsById: (id, data) => axios({ url: `/api/product1/v1/goods/${id}`, method: 'put', data }), // 6. 下载货物报表 downloadGoodsReport: (data) => axios({ url: `/api/product1/v1/goods/report`, method: 'post', data, responseType: 'blob' // browser only: 'blob' }), // 7. 上传货物报表 uploadGoodsReport: (data) => axios({ url: `/api/product1/v1/goods/up_report`, method: 'post', data, headers:{"Content-Type": "multipart/form-data"} }) } export default goods
以上,展示了常用的请求形式,包括:GET, POST, PUT/PATCH, DELETE。
补充几点:
url = base url + request url
,其中 base url 就是在 5.1 节中,axios 实例上的 baseURL: process.env.VUE_APP_BASE_URL- 第五条更新数据,用了 PUT,其实还有一个类似的 PATCH,他们的区别在上面已经给了注释。PUT 和 PATCH 的区别:stackoverflow.com/questions/2…
- 发送 POST 请求使用 data ,而发送 GET 请求时,可能需要带上 url 参数,这时候就要用到 params 了。
举个请求购物车分页数据的例子:http://xxx.yyy.zzz:3000/api/product1/v1/carts?page=1&size=-1
// src/api/carts/index.js import axios from '@/utils/request' const carts = { // ... // 获取购物车信息 getCarts: (params) => axios({ url: '/api/product1/v1/carts', method: 'get', params }) } export default carts
有时候我们还会看到,在发送 POST 请求时,有人会使用 qs ,它的作用是序列化 JSON 格式数据为 urlencoded 格式的字符串。
原因是后端语言处理 JSON 格式的数据比较麻烦,例如 Go 语言的 Gin 框架,传递给它 {Country: Brasil, City: Belo Horizonte}
,为了安全起见,需要开发人员据此单独做结构体,而传递 'Country=Brasil&City=Belo Horizonte'
这样的字符串则更为合适。
这里改写一下货物模块中的第三个 POST 请求,记得首先要引入 qs 模块:
import axios from '@/utils/request' + import qs from 'qs' const goods = { // ... // 3. 新增一条货物信息 addGoods: (data) => axios({ url: '/api/product1/v1/goods', method: 'post', data: qs.stringify(data) }), // ... } export default goods
- 关于urlencoded可以查看以下三篇参考:
6.3 导出 API 对象
当写完各个模块的 api 请求接口后,就需要导出了。回到第二节的构思,我们想要通过 this.$api.moduleName.methodName(…args) 的形式调用方法发起请求。
进入 src/api/index.js
import user from './user' import goods from './goods' import carts from './carts' // ... class API { constructor () { this.user = user this.goods = goods this.carts = carts // ... } } // 导出使用 export default new API()
6.4 小试牛刀
将 API 实例导出之后,将它挂载到 Vue 原型上,就能正常使用了。
// src/main.js import API from './api' Vue.prototype.$api = API
// Test.vue <template> <button @click="handleData">test request</button> </template> <script> export default { name: 'Test', data () { return { tableData: [] } }, methods: { fetchData () { return this.$api.goods.getGoods() }, async handleData () { try { const response = await this.fetchData() // ... } catch (e) { console.error(e) } } } } </script>
6.5 冗余问题的解决
以上,其实还有一个问题尚未解决,就是每一个请求 api 的 url 里的 /api/product1/v1
都是重复的,这个容易,把它分离出去变成一个模块,自定义 url 前缀。
src/api/url_prefixes.js
function generatePrefix ({productName, version} = {productName: 'product1', version: 'v1'}) { return `/api/${productName}/${version}` } export const v1 = generatePrefix() // '/api/product1/v1' export const v2 = generatePrefix({version: 'v2'}) // '/api/product1/v2' export const p2v2 = generatePrefix({productName: 'product2', version: 'v2'}) // '/api/product2/v2'
从上面可以看到,除了接口版本可能会发生变化,产品名称也是有可能发生变化的,我们也用变量控制。之后,在各模块的 api 文件中就可以用使用变量来控制 url 前缀部分了。
例如改写货物模块的 api url 前缀:
import axios from '@/utils/request' // axios 实例引入(第五节封装的) import { v1 } from '../**url_prefixes**.js' // 引入前缀:**'/api/product1/v1'** const goods = { // 1. 获取所有货物信息 getGoods: () => axios({ url: `${v1}/v1/goods`, method: 'get' }), // 2. 获取某条货物信息 getGoodsById: (id) => axios({ url: `${v1}/goods/${id}`, method: 'get' }), // 3. 新增一条货物信息 addGoods: (data) => axios({ url: `${v1}/v1/goods`, method: 'post', data }), // ... } export default goods
至此,三个关键问题都已经得到了解决:
- 永久性变量冗余
- 高强度心智负担
- 模块过于扁平化
七、总结
请求的 API 封装到此就结束了,除了以上这种面向对象的设计以外,还有其他的方式。最后,整理一下封装的整体流程:
- 请求 API 结构设计,this.$api.moduleName.methodName(…args)
- 脚手架模式与环境变量设置
- Axios 封装(包括实例配置和拦截器),导出实例
- 请求 API 编写及导出,注意分模块编写
- 将 API 挂载到 Vue 实例,正常使用
文章里,我添加了很多参考链接,如有疑惑可以看一看,看完如果仍然有疑惑,那就找人讨论吧,之后这一块内容你应该就可以掌握了。