We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
开门见山地说,这篇文章是一篇安利软文~,安利的对象就是最近搞的 tua-api。
顾名思义,这就是一款辅助获取接口数据的工具。
发请求相关的工具辣么多,那我为啥要用你呢?
理想状态下,项目中应该有一个 api 中间层。各种接口在这里定义,业务侧不应该手动编写接口地址,而应该调用接口层导出的函数。
import { fooApi } from '@/apis/' fooApi .bar({ a: '1', b: '2' }) // 发起请求,a、b 是请求参数 .then(console.log) // 收到响应 .catch(console.error) // 处理错误
那么如何组织实现这个 api 中间层呢?这里涉及两方面:
让我们先回顾一下有关发请求的历史。
说到发请求,最经典的方式莫过于调用浏览器原生的 XHR。在此不赘述,有兴趣可以看看MDN 上的文档。
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()
这代码都不用看,想想就头皮发麻...
由于原生 XHR 写起来太繁琐,再加上当时 jQuery 如日中天。日常开发中用的比较多的还是 jQuery 提供的 ajax 方法。jQuery ajax 文档点这里
var params = { url: 'some url', data: { name: 'Steve', location: 'Beijing' }, } $.ajax(params) .done(console.log) .fail(console.error)
jQuery 不仅封装了 XHR,还十分贴心地提供跨域的 jsonp 功能。
$.ajax({ url: 'some url', data: { name: 'Steve', location: 'Beijing' }, dataType: 'jsonp', success: console.log, error: console.error, })
讲道理,jQuery 的 ajax 已经很好用了。然而随着 Vue、React、Angular 的兴起,连 jQuery 本身都被革命了。新项目为了发个请求还引入巨大的 jQuery 肯定不合理,当然后面这些替代方案也功不可没...
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,当然其他库也可以)
fetch-jsonp
综上,日常开发在框架的使用上以 axios 为主,实在不得不发 jsonp 请求时,就用 fetch-jsonp。这就是我们中间层的基础,即“武器”部分。
axios
在小程序场景没得选,只能使用官方的 wx.request 函数...
wx.request
对于简单的页面,直接裸写请求地址也没毛病。但是一旦项目变大,页面数量也上去了,直接在页面,或是组件中裸写接口的话,会带来以下问题
如何封装这些接口呢?
首先我们来分析一下接口地址的组成
https://example-base.com/foo/create
https://example-base.com/foo/modify
https://example-base.com/foo/delete
对于以上地址,在 tua-api 中一般将其分为3部分
tua-api
'https://example-base.com/'
'foo'
[ 'create', 'modify', 'delete' ]
apis/ 一般是这样的文件结构:
apis/
. └── apis ├── prefix-1.js ├── prefix-2.js ├── foo.js // <-- 以上的 api 地址会放在这里 └── index.js
index.js 作为接口层的入口,会导入并生成各个 api 然后再导出。
index.js
所以以上的示例接口地址可以这么写
// 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 该怎么写:
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 属性,将接口重命名一下。
name
// src/apis/foo.js export default { // ... pathList: [ // ... { path: 'remove/all', name: 'removeAll' }, { path: 'add-array', name: 'addArray' }, ], }
更多配置请点击这里查看
一个接口层仅仅只能发 api 请求是远远不够的,在日常使用中往往还有以下需求
小程序端由于原生自带 UI 组件,所以框架内置了该功能。主要包括以下参数
顾名思义,就是开关和具体的显示、隐藏的方法,详情参阅这里
最简单的钩子函数就是 beforeFn/afterFn 这俩函数了。
beforeFn/afterFn
beforeFn 是在请求发起前执行的函数(例如小程序可以通过返回 header 传递 cookie),因为是通过 beforeFn().then(...) 调用,所以注意要返回 Promise。
beforeFn().then(...)
afterFn 是在收到响应后执行的函数,可以不用返回 Promise。
注意接收的参数是一个【数组】 [ res.data, ctx ]
[ res.data, ctx ]
所以默认值是 const afterFn = ([x]) => x,即返回接口数据到业务侧
const afterFn = ([x]) => x
{ code, data, msg }
钩子函数有时不太够用,并且代码一长不太好维护。所以 tua-api 还引入了中间件功能,用法上和 koa 的中间件很像(其实底层直接用了 koa-compose)。
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 各位开发者老爷们不来了解一下么?
The text was updated successfully, but these errors were encountered:
No branches or pull requests
零、问题的由来
开门见山地说,这篇文章是一篇安利软文~,安利的对象就是最近搞的 tua-api。
顾名思义,这就是一款辅助获取接口数据的工具。
理想状态下,项目中应该有一个 api 中间层。各种接口在这里定义,业务侧不应该手动编写接口地址,而应该调用接口层导出的函数。
那么如何组织实现这个 api 中间层呢?这里涉及两方面:
让我们先回顾一下有关发请求的历史。
一、如何发请求
1.1.原生 XHR (XMLHttpRequest)
说到发请求,最经典的方式莫过于调用浏览器原生的 XHR。在此不赘述,有兴趣可以看看MDN 上的文档。
这代码都不用看,想想就头皮发麻...
1.2.jQuery 封装的 ajax
由于原生 XHR 写起来太繁琐,再加上当时 jQuery 如日中天。日常开发中用的比较多的还是 jQuery 提供的 ajax 方法。jQuery ajax 文档点这里
jQuery 不仅封装了 XHR,还十分贴心地提供跨域的 jsonp 功能。
讲道理,jQuery 的 ajax 已经很好用了。然而随着 Vue、React、Angular 的兴起,连 jQuery 本身都被革命了。新项目为了发个请求还引入巨大的 jQuery 肯定不合理,当然后面这些替代方案也功不可没...
1.3.现代浏览器的原生 fetch
XHR 是一个设计粗糙的 API。记得当年笔试某部门的实习生的时候就有手写 XHR 的题目,我反正记不住 api,并没有写出来...
fetch api 基于 Promise 设计,调用起来比 XHR 方便多了。
async/await 自然也能使用
当然 fetch 也有不少的问题
1.4.基于 Promise 的 axios
axios 算是请求框架中的明星项目了。目前 github 5w+ 的 star...
先来看看有什么特性吧~
嗯,看起来确实是居家旅行全栈开发必备好库,但是 axios 并不支持 jsonp...
1.5.不得不用的 jsonp
在服务器端不方便配置跨域头的情况下,采用 jsonp 的方式发起跨域请求是一种常规操作。
在此不探究具体的实现,原理上来说就是
callback({ "foo": "bar" })
。上面讲到新项目一般都弃用 jQuery 了,那么跨域请求还是得发呀。所以可能你还需要一个发送 jsonp 的库。(实践中选了
fetch-jsonp
,当然其他库也可以)综上,日常开发在框架的使用上以
axios
为主,实在不得不发 jsonp 请求时,就用fetch-jsonp
。这就是我们中间层的基础,即“武器”部分。1.6.小程序场景
在小程序场景没得选,只能使用官方的
wx.request
函数...二、构建接口层基础功能
对于简单的页面,直接裸写请求地址也没毛病。但是一旦项目变大,页面数量也上去了,直接在页面,或是组件中裸写接口的话,会带来以下问题
2.1.接口地址划分
首先我们来分析一下接口地址的组成
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' ]
2.2.文件结构
apis/
一般是这样的文件结构:index.js
作为接口层的入口,会导入并生成各个 api 然后再导出。2.3.基础配置内容
所以以上的示例接口地址可以这么写
这时如果想修改服务器地址,只需要修改 host 即可。甚至还能这么玩
2.4.配置导出
下面来看一下
apis/index.js
该怎么写:这样我们就把接口地址封装了起来,业务侧不需要关心接口的逻辑,而后期接口的修改和升级时只需要修改这里的配置即可。
2.5.接口参数与接口类型
示例的接口地址太理想化了,如果有参数如何传递?
假设以上接口添加 id、from 和 foo 参数。并且增加以下逻辑:
bar
index-page
delete-page
哎~,别急着死,暂且看看怎么用
tua-api
来抽象这些逻辑?现在来看看业务侧代码有什么变化。
2.6.接口重命名
假设现在后台又添加了以下两个新接口,咱们该怎么写配置呢?
remove/all
add-array
首先,把后台同学砍死...2333这什么鬼接口地址,直接填的话会业务侧就会写成这样。
这代码简直无法直视...让我们用
name
属性,将接口重命名一下。更多配置请点击这里查看
三、高级功能
一个接口层仅仅只能发 api 请求是远远不够的,在日常使用中往往还有以下需求
3.1.小程序端的 loading 展示
小程序端由于原生自带 UI 组件,所以框架内置了该功能。主要包括以下参数
顾名思义,就是开关和具体的显示、隐藏的方法,详情参阅这里
3.2.基础钩子函数
最简单的钩子函数就是
beforeFn/afterFn
这俩函数了。beforeFn 是在请求发起前执行的函数(例如小程序可以通过返回 header 传递 cookie),因为是通过
beforeFn().then(...)
调用,所以注意要返回 Promise。afterFn 是在收到响应后执行的函数,可以不用返回 Promise。
{ code, data, msg }
3.3.middleware 中间件
钩子函数有时不太够用,并且代码一长不太好维护。所以 tua-api 还引入了中间件功能,用法上和 koa 的中间件很像(其实底层直接用了
koa-compose
)。首先说下中间件执行顺序,koa 中间件的执行顺序和 redux 的正好相反,例如以上写法会以以下顺序执行:
请求参数 -> fn1 -> fn2 -> fn3 -> 响应数据 -> fn3 -> fn2 -> fn1
简单说下中间件的写法,分为两种
return next()
否则Promise
链就断了!await next()
!其他参数参阅这里
四、小结
这篇安利文,先是从前端发请求的历史出发。一步步介绍了如何构建以及使用 api 中间层,来统一管理接口地址,最后还介绍了下中间件等高级功能。话说回来,这么好用的 tua-api 各位开发者老爷们不来了解一下么?
参考文献
The text was updated successfully, but these errors were encountered: