开门见山地说,这篇文章是一篇安利软文~,安利的对象就是最近搞的 tua-api。html
顾名思义,这就是一款辅助获取接口数据的工具。前端
发请求相关的工具辣么多,那我为啥要用你呢?
理想状态下,项目中应该有一个 api 中间层。各类接口在这里定义,业务侧不该该手动编写接口地址,而应该调用接口层导出的函数。jquery
import { fooApi } from '@/apis/' fooApi .bar({ a: '1', b: '2' }) // 发起请求,a、b 是请求参数 .then(console.log) // 收到响应 .catch(console.error) // 处理错误
那么如何组织实现这个 api 中间层呢?这里涉及两方面:ios
让咱们先回顾一下有关发请求的历史。git
说到发请求,最经典的方式莫过于调用浏览器原生的 XHR。在此不赘述,有兴趣能够看看MDN 上的文档。github
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() // 在万恶的 IE 上可能尚未 XMLHttpRequest 这对象 : new ActiveXObject('Microsoft.XMLHTTP') xhr.open('GET', 'some url') xhr.responseType = 'json' // 传统使用 onreadystatechange xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText) } } // 或者直接使用 onload 事件 xhr.onload = function () { console.log(xhr.response) } // 处理出错 xhr.onerror = console.error xhr.send()
这代码都不用看,想一想就头皮发麻...ajax
因为原生 XHR 写起来太繁琐,再加上当时 jQuery 如日中天。平常开发中用的比较多的仍是 jQuery 提供的 ajax 方法。jQuery ajax 文档点这里json
var params = { url: 'some url', data: { name: 'Steve', location: 'Beijing' }, } $.ajax(params) .done(console.log) .fail(console.error)
jQuery 不只封装了 XHR,还十分贴心地提供跨域的 jsonp 功能。redux
$.ajax({ url: 'some url', data: { name: 'Steve', location: 'Beijing' }, dataType: 'jsonp', success: console.log, error: console.error, })
讲道理,jQuery 的 ajax 已经很好用了。然而随着 Vue、React、Angular 的兴起,连 jQuery 自己都被革命了。新项目为了发个请求还引入巨大的 jQuery 确定不合理,固然后面这些替代方案也功不可没...axios
XHR 是一个设计粗糙的 API。记得当年笔试某部门的实习生的时候就有手写 XHR 的题目,我反正记不住 api,并无写出来...
fetch api 基于 Promise 设计,调用起来比 XHR 方便多了。
fetch(url) .then(res => res.json()) .then(console.log) .catch(console.error)
async/await 天然也能使用
try { const data = await fetch(url).then(res => res.json()) console.log(data) } catch (e) { console.error(e) }
固然 fetch 也有很多的问题
axios 算是请求框架中的明星项目了。目前 github 5w+ 的 star...
先来看看有什么特性吧~
嗯,看起来确实是居家旅行全栈开发必备好库,可是 axios 并不支持 jsonp...
在服务器端不方便配置跨域头的状况下,采用 jsonp 的方式发起跨域请求是一种常规操做。
在此不探究具体的实现,原理上来讲就是
callback({ "foo": "bar" })
。上面讲到新项目通常都弃用 jQuery 了,那么跨域请求仍是得发呀。因此可能你还须要一个发送 jsonp 的库。(实践中选了 fetch-jsonp
,固然其余库也能够)
综上,平常开发在框架的使用上以 axios
为主,实在不得不发 jsonp 请求时,就用 fetch-jsonp
。这就是咱们中间层的基础,即“武器”部分。
在小程序场景没得选,只能使用官方的 wx.request
函数...
对于简单的页面,直接裸写请求地址也没毛病。可是一旦项目变大,页面数量也上去了,直接在页面,或是组件中裸写接口的话,会带来如下问题
如何封装这些接口呢?
首先咱们来分析一下接口地址的组成
https://example-base.com/foo/create
https://example-base.com/foo/modify
https://example-base.com/foo/delete
对于以上地址,在 tua-api
中通常将其分为3部分
'https://example-base.com/'
'foo'
[ 'create', 'modify', 'delete' ]
apis/
通常是这样的文件结构:
. └── apis ├── prefix-1.js ├── prefix-2.js ├── foo.js // <-- 以上的 api 地址会放在这里 └── index.js
index.js
做为接口层的入口,会导入并生成各个 api 而后再导出。
因此以上的示例接口地址能够这么写
// src/apis/foo.js export default { // 请求的公用服务器地址。 host: 'http://example-base.com/', // 请求的中间路径,建议与文件同名,以便后期维护。 prefix: 'foo', // 接口地址数组 pathList: [ { path: 'create' }, { path: 'modify' }, { path: 'delete' }, ], }
这时若是想修改服务器地址,只须要修改 host 便可。甚至还能这么玩
// src/apis/foo.js // 某个获取页面地址参数的函数 const getUrlParams = () => {...} export default { // 根据 NODE_ENV 采用不一样的服务器 host: process.env.NODE_ENV === 'test' ? 'http://example-test.com/' : 'http://example-base.com/', // 根据页面参数采用不一样的服务器,即页面地址带 ?test=1 则走测试地址 host: getUrlParams().test ? 'http://example-test.com/' : 'http://example-base.com/', // ... }
下面来看一下 apis/index.js
该怎么写:
import TuaApi from 'tua-api' // 初始化 const tuaApi = new TuaApi({ ... }) // 导出 export const fooApi = tuaApi.getApi(require('./foo').default)
这样咱们就把接口地址封装了起来,业务侧不须要关心接口的逻辑,然后期接口的修改和升级时只须要修改这里的配置便可。
示例的接口地址太理想化了,若是有参数如何传递?
假设以上接口添加 id、from 和 foo 参数。而且增长如下逻辑:
bar
index-page
delete-page
哎~,别急着死,暂且看看怎么用 tua-api
来抽象这些逻辑?
// src/apis/foo.js export default { // ... // 公共参数,将会合并到后面的各个接口参数中 commonParams: { foo: 'bar', from: 'index-page', }, pathList: [ { path: 'create', params: { // 相似 Vue 中 props 的类型检查 id: { required: true }, }, }, { path: 'modify', // 使用 post 的方式 type: 'post', params: { // 写成 isRequired 也行 id: { isRequired: true }, // 接口不合并公共参数,即不传 from 参数 commonParams: null, }, }, { path: 'delete', // 使用 jsonp 的方式(不填则默认使用 axios) reqType: 'jsonp', params: { id: { required: true }, // 这里填写的 from 会覆盖 commonParams 中的同名属性 from: 'delete-page', }, }, ], }
如今来看看业务侧代码有什么变化。
import { fooApi } from '@/apis/' // 直接调用将会报错,由于没有传递 id 参数 await fooApi.create() // 请求参数使用传入的 from:id=1&foo=bar&from=foo-page await fooApi.create({ id: 1, from: 'foo-page' }) // 请求参数将只有 id:id=1 await fooApi.modify({ id: 1 }) // 请求参数将使用自身的 from:id=1&foo=bar&from=delete-page await fooApi.delete({ id: 1 })
假设如今后台又添加了如下两个新接口,我们该怎么写配置呢?
remove/all
add-array
首先,把后台同窗砍死...2333
这什么鬼接口地址,直接填的话会业务侧就会写成这样。
fooApi['remove/all'] fooApi['add-array']
这代码简直没法直视...让咱们用 name
属性,将接口重命名一下。
// src/apis/foo.js export default { // ... pathList: [ // ... { path: 'remove/all', name: 'removeAll' }, { path: 'add-array', name: 'addArray' }, ], }
一个接口层仅仅只能发 api 请求是远远不够的,在平常使用中每每还有如下需求
小程序端因为原生自带 UI 组件,因此框架内置了该功能。主要包括如下参数
顾名思义,就是开关和具体的显示、隐藏的方法,详情参阅这里
最简单的钩子函数就是 beforeFn/afterFn
这俩函数了。
beforeFn 是在请求发起前执行的函数(例如小程序能够经过返回 header 传递 cookie),由于是经过 beforeFn().then(...)
调用,因此注意要返回 Promise。
afterFn 是在收到响应后执行的函数,能够不用返回 Promise。
注意接收的参数是一个【数组】[ res.data, ctx ]
因此默认值是
const afterFn = ([x]) => x
,即返回接口数据到业务侧
{ code, data, msg }
钩子函数有时不太够用,而且代码一长不太好维护。因此 tua-api 还引入了中间件功能,用法上和 koa 的中间件很像(其实底层直接用了 koa-compose
)。
export default { middleware: [ fn1, fn2, fn3 ], }
首先说下中间件执行顺序,koa 中间件的执行顺序和 redux 的正好相反,例如以上写法会以如下顺序执行:
请求参数 -> fn1 -> fn2 -> fn3 -> 响应数据 -> fn3 -> fn2 -> fn1
简单说下中间件的写法,分为两种
return next()
不然 Promise
链就断了!await next()
!// 普通函数,注意必定要 return next() function (ctx, next) { ctx.req // 请求的各类配置 ctx.res // 响应,但这时还未发起请求,因此是 undefined! ctx.startTime // 发起请求的时间 // 传递控制权给下一个中间件 return next().then(() => { // 注意这里才有响应! ctx.res // 响应对象 ctx.res.data // 响应的数据 ctx.reqTime // 请求花费的时间 ctx.endTime // 收到响应的时间 }) } // async/await async function (ctx, next) { ctx.req // 请求的各类配置 // 传递控制权给下一个中间件 await next() // 注意这里才有响应响应! ctx.res // 响应对象 }
这篇安利文,先是从前端发请求的历史出发。一步步介绍了如何构建以及使用 api 中间层,来统一管理接口地址,最后还介绍了下中间件等高级功能。话说回来,这么好用的 tua-api 各位开发者老爷们不来了解一下么?