处理请求 url 参数
需求分析
还记得我们上节课遗留了一个问题,再来看这个例子:
1 2 3 4 5 6 7 8
| axios({ method: 'get', url: '/base/get', params: { a: 1, b: 2 } })
|
我们希望最终请求的 url 是 /base/get?a=1&b=2,这样服务端就可以通过请求的 url 解析到我们传来的参数数据了。实际上就是把 params 对象的 key 和 value 拼接到 url 上。
再来看几个更复杂的例子。
参数值为数组
1 2 3 4 5 6 7
| axios({ method: 'get', url: '/base/get', params: { foo: ['bar', 'baz'] } })
|
最终请求的 url 是 /base/get?foo[]=bar&foo[]=baz'。
参数值为对象
1 2 3 4 5 6 7 8 9
| axios({ method: 'get', url: '/base/get', params: { foo: { bar: 'baz' } } })
|
最终请求的 url 是 /base/get?foo=%7B%22bar%22:%22baz%22%7D,foo 后面拼接的是 {"bar":"baz"} encode 后的结果。
参数值为 Date 类型
1 2 3 4 5 6 7 8 9
| const date = new Date()
axios({ method: 'get', url: '/base/get', params: { date } })
|
最终请求的 url 是 /base/get?date=2019-04-01T05:55:39.030Z,date 后面拼接的是 date.toISOString() 的结果。
特殊字符支持
对于字符 @、:、$、,、 、[、],我们是允许出现在 url 中的,不希望被 encode。
1 2 3 4 5 6 7
| axios({ method: 'get', url: '/base/get', params: { foo: '@:$, ' } })
|
最终请求的 url 是 /base/get?foo=@:$+,注意,我们会把空格 转换成 +。
空值忽略
对于值为 null 或者 undefined 的属性,我们是不会添加到 url 参数中的。
1 2 3 4 5 6 7 8
| axios({ method: 'get', url: '/base/get', params: { foo: 'bar', baz: null } })
|
最终请求的 url 是 /base/get?foo=bar。
丢弃 url 中的哈希标记
1 2 3 4 5 6 7
| axios({ method: 'get', url: '/base/get#hash', params: { foo: 'bar' } })
|
最终请求的 url 是 /base/get?foo=bar
保留 url 中已存在的参数
1 2 3 4 5 6 7
| axios({ method: 'get', url: '/base/get?foo=bar', params: { bar: 'baz' } })
|
最终请求的 url 是 /base/get?foo=bar&bar=baz
buildURL 函数实现
根据我们之前的需求分析,我们要实现一个工具函数,把 params 拼接到 url 上。我们希望把项目中的一些工具函数、辅助方法独立管理,于是我们创建一个 helpers 目录,在这个目录下创建 url.ts 文件,未来会把处理 url 相关的工具函数都放在该文件中。
helpers/url.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import { isDate, isObject } from './util'
function encode (val: string): string { return encodeURIComponent(val) .replace(/%40/g, '@') .replace(/%3A/gi, ':') .replace(/%24/g, '$') .replace(/%2C/gi, ',') .replace(/%20/g, '+') .replace(/%5B/gi, '[') .replace(/%5D/gi, ']') }
export function bulidURL (url: string, params?: any) { if (!params) { return url }
const parts: string[] = []
Object.keys(params).forEach((key) => { let val = params[key] if (val === null || typeof val === 'undefined') { return } let values: string[] if (Array.isArray(val)) { values = val key += '[]' } else { values = [val] } values.forEach((val) => { if (isDate(val)) { val = val.toISOString() } else if (isObject(val)) { val = JSON.stringify(val) } parts.push(`${encode(key)}=${encode(val)}`) }) })
let serializedParams = parts.join('&')
if (serializedParams) { const markIndex = url.indexOf('#') if (markIndex !== -1) { url = url.slice(0, markIndex) }
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams }
return url }
|
helpers/util.ts:
1 2 3 4 5 6 7 8 9 10
| const toString = Object.prototype.toString
export function isDate (val: any): val is Date { return toString.call(val) === '[object Date]' }
export function isObject (val: any): val is Object { return val !== null && typeof val === 'object' }
|
实现 url 参数处理逻辑
我们已经实现了 buildURL 函数,接下来我们来利用它实现 url 参数的处理逻辑。
在 index.ts 文件中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function axios (config: AxiosRequestConfig): void { processConfig(config) xhr(config) }
function processConfig (config: AxiosRequestConfig): void { config.url = transformUrl(config) }
function transformUrl (config: AxiosRequestConfig): string { const { url, params } = config return bulidURL(url, params) }
|
在执行 xhr 函数前,我们先执行 processConfig 方法,对 config 中的数据做处理,除了对 url 和 params 处理之外,未来还会处理其它属性。
在 processConfig 函数内部,我们通过执行 transformUrl 函数修改了 config.url,该函数内部调用了 buildURL。
那么至此,我们对 url 参数处理逻辑就实现完了,接下来我们就开始编写 demo 了。
demo 编写
在 examples 目录下创建 base 目录,在 base 目录下创建 index.html:
1 2 3 4 5 6 7 8 9 10
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Base example</title> </head> <body> <script src="/__build__/base.js"></script> </body> </html>
|
接着创建 app.ts 作为入口文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import axios from '../../src/index'
axios({ method: 'get', url: '/base/get', params: { foo: ['bar', 'baz'] } })
axios({ method: 'get', url: '/base/get', params: { foo: { bar: 'baz' } } })
const date = new Date()
axios({ method: 'get', url: '/base/get', params: { date } })
axios({ method: 'get', url: '/base/get', params: { foo: '@:$, ' } })
axios({ method: 'get', url: '/base/get', params: { foo: 'bar', baz: null } })
axios({ method: 'get', url: '/base/get#hash', params: { foo: 'bar' } })
axios({ method: 'get', url: '/base/get?foo=bar', params: { bar: 'baz' } })
|
接着在 server.js 添加新的接口路由:
1 2 3
| router.get('/base/get', function(req, res) { res.json(req.query) })
|
然后在命令行运行 npm run dev,接着打开 chrome 浏览器,访问 http://localhost:8080/ 即可访问我们的 demo 了,我们点到 Base 目录下,通过开发者工具的 network 部分我们可以看到成功发送的多条请求,并可以观察它们最终请求的 url,已经如期添加了请求参数。
那么至此我们的请求 url 参数处理编写完了,下一小节我们会对 request body 数据做处理。
来源:本文导入自 xugaoyi/vuepress-theme-vdoing 的 docs/《TypeScript 从零实现 axios》/04.ts-axios 基础功能实现/01.处理请求 url 参数.md。
原作者:HuangYi。许可证:MIT。