需求分析
还记得我们上节课遗留了一个问题,再来看这个例子:
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get',
4 params: {
5 a: 1,
6 b: 2
7 }
8})
我们希望最终请求的 url 是 /base/get?a=1&b=2,这样服务端就可以通过请求的 url 解析到我们传来的参数数据了。实际上就是把 params 对象的 key 和 value 拼接到 url 上。
再来看几个更复杂的例子。
参数值为数组
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get',
4 params: {
5 foo: ['bar', 'baz']
6 }
7})
最终请求的 url 是 /base/get?foo[]=bar&foo[]=baz'。
参数值为对象
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get',
4 params: {
5 foo: {
6 bar: 'baz'
7 }
8 }
9})
最终请求的 url 是 /base/get?foo={"bar":"baz"},foo 后面拼接的是 {"bar":"baz"} encode 后的结果。
参数值为 Date 类型
代码语言:javascript复制1const date = new Date()
2
3axios({
4 method: 'get',
5 url: '/base/get',
6 params: {
7 date
8 }
9})
最终请求的 url 是 /base/get?date=2019-04-01T05:55:39.030Z,date 后面拼接的是 date.toISOString() 的结果。
特殊字符支持
对于字符 @、:、
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get',
4 params: {
5 foo: '@:$, '
6 }
7})
最终请求的 url 是 /base/get?foo=@:、,、、[、],我们是允许出现在url中的,不希望被encode。¨K5K最终请求的url是/base/get?foo=@: ,注意,我们会把空格 转换成 。
空值忽略
对于值为 null 或者 undefined 的属性,我们是不会添加到 url 参数中的。
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get',
4 params: {
5 foo: 'bar',
6 baz: null
7 }
8})
最终请求的 url 是 /base/get?foo=bar。
丢弃 url 中的哈希标记
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get#hash',
4 params: {
5 foo: 'bar'
6 }
7})
最终请求的 url 是 /base/get?foo=bar
保留 url 中已存在的参数
代码语言:javascript复制1axios({
2 method: 'get',
3 url: '/base/get?foo=bar',
4 params: {
5 bar: 'baz'
6 }
7})
最终请求的 url 是 /base/get?foo=bar&bar=baz
buildURL 函数实现
根据我们之前的需求分析,我们要实现一个工具函数,把 params 拼接到 url 上。我们希望把项目中的一些工具函数、辅助方法独立管理,于是我们创建一个 helpers 目录,在这个目录下创建 url.ts 文件,未来会把处理 url 相关的工具函数都放在该文件中。
helpers/url.ts:
代码语言:javascript复制 1import { isDate, isObject } from './util'
2
3function encode (val: string): string {
4 return encodeURIComponent(val)
5 .replace(/@/g, '@')
6 .replace(/:/gi, ':')
7 .replace(/$/g, '$')
8 .replace(/,/gi, ',')
9 .replace(/ /g, ' ')
10 .replace(/[/gi, '[')
11 .replace(/]/gi, ']')
12}
13
14export function bulidURL (url: string, params?: any) {
15 if (!params) {
16 return url
17 }
18
19 const parts: string[] = []
20
21 Object.keys(params).forEach((key) => {
22 let val = params[key]
23 if (val === null || typeof val === 'undefined') {
24 return
25 }
26 let values: string[]
27 if (Array.isArray(val)) {
28 values = val
29 key = '[]'
30 } else {
31 values = [val]
32 }
33 values.forEach((val) => {
34 if (isDate(val)) {
35 val = val.toISOString()
36 } else if (isObject(val)) {
37 val = JSON.stringify(val)
38 }
39 parts.push(`${encode(key)}=${encode(val)}`)
40 })
41 })
42
43 let serializedParams = parts.join('&')
44
45 if (serializedParams) {
46 const markIndex = url.indexOf('#')
47 if (markIndex !== -1) {
48 url = url.slice(0, markIndex)
49 }
50
51 url = (url.indexOf('?') === -1 ? '?' : '&') serializedParams
52 }
53
54 return url
55}
helpers/util.ts:
代码语言:javascript复制1const toString = Object.prototype.toString
2
3export function isDate (val: any): val is Date {
4 return toString.call(val) === '[object Date]'
5}
6
7export function isObject (val: any): val is Object {
8 return val !== null && typeof val === 'object'
9}
实现 url 参数处理逻辑
我们已经实现了 buildURL 函数,接下来我们来利用它实现 url 参数的处理逻辑。
在 index.ts 文件中添加如下代码:
代码语言:javascript复制 1function axios (config: AxiosRequestConfig): void {
2 processConfig(config)
3 xhr(config)
4}
5
6function processConfig (config: AxiosRequestConfig): void {
7 config.url = transformUrl(config)
8}
9
10function transformUrl (config: AxiosRequestConfig): string {
11 const { url, params } = config
12 return bulidURL(url, params)
13}
在执行 xhr 函数前,我们先执行 processConfig 方法,对 config 中的数据做处理,除了对 url 和 params 处理之外,未来还会处理其它属性。
在 processConfig 函数内部,我们通过执行 transformUrl 函数修改了 config.url,该函数内部调用了 buildURL。
那么至此,我们对 url 参数处理逻辑就实现完了,接下来我们就开始编写 demo 了。
demo 编写
在 examples 目录下创建 base 目录,在 base 目录下创建 index.html:
代码语言:javascript复制 1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Base example</title>
6 </head>
7 <body>
8 <script src="/__build__/base.js"></script>
9 </body>
10</html>
接着创建 app.ts 作为入口文件:
代码语言:javascript复制 1import axios from '../../src/index'
2
3axios({
4 method: 'get',
5 url: '/base/get',
6 params: {
7 foo: ['bar', 'baz']
8 }
9})
10
11axios({
12 method: 'get',
13 url: '/base/get',
14 params: {
15 foo: {
16 bar: 'baz'
17 }
18 }
19})
20
21const date = new Date()
22
23axios({
24 method: 'get',
25 url: '/base/get',
26 params: {
27 date
28 }
29})
30
31axios({
32 method: 'get',
33 url: '/base/get',
34 params: {
35 foo: '@:$, '
36 }
37})
38
39axios({
40 method: 'get',
41 url: '/base/get',
42 params: {
43 foo: 'bar',
44 baz: null
45 }
46})
47
48axios({
49 method: 'get',
50 url: '/base/get#hash',
51 params: {
52 foo: 'bar'
53 }
54})
55
56axios({
57 method: 'get',
58 url: '/base/get?foo=bar',
59 params: {
60 bar: 'baz'
61 }
62})
接着在 server.js 添加新的接口路由:
1router.get('/base/get', function(req, res) { 2 res.json(req.query) 3})
然后在命令行运行 npm run dev,接着打开 chrome 浏览器,访问 http://localhost:8080/ 即可访问我们的 demo 了,我们点到 Base 目录下,通过开发者工具的 network 部分我们可以看到成功发送的多条请求,并可以观察它们最终请求的 url,已经如期添加了请求参数。
那么至此我们的请求 url 参数处理编写完了,下面我们对request body部分进行处理。
需求分析
我们通过执行 XMLHttpRequest 对象实例的 send 方法来发送请求,并通过该方法的参数设置请求 body 数据,我们可以去 mdn 查阅该方法支持的参数类型。
我们发现 send 方法的参数支持 Document 和 BodyInit 类型,BodyInit 包括了 Blob, BufferSource, FormData, URLSearchParams, ReadableStream、USVString,当没有数据的时候,我们还可以传入 null。
但是我们最常用的场景还是传一个普通对象给服务端,例如:
代码语言:javascript复制1axios({
2 method: 'post',
3 url: '/base/post',
4 data: {
5 a: 1,
6 b: 2
7 }
8})
这个时候 data是不能直接传给 send 函数的,我们需要把它转换成 JSON 字符串。
transformRequest 函数实现
根据需求分析,我们要实现一个工具函数,对 request 中的 data 做一层转换。我们在 helpers 目录新建 data.ts 文件。
helpers/data.ts:
代码语言:javascript复制1import { isPlainObject } from './util'
2
3export function transformRequest (data: any): any {
4 if (isPlainObject(data)) {
5 return JSON.stringify(data)
6 }
7 return data
8}
helpers/util.js:
1export function isPlainObject (val: any): val is Object { 2 return toString.call(val) === '[object Object]' 3}
这里为什么要使用 isPlainObject 函数判断,而不用之前的 isObject 函数呢,因为 isObject 的判断方式,对于 FormData、ArrayBuffer 这些类型,isObject 判断也为 true,但是这些类型的数据我们是不需要做处理的,而 isPlainObject 的判断方式,只有我们定义的普通 JSON 对象才能满足。
helpers/url.ts:
代码语言:javascript复制1if (isDate(val)) {
2 val = val.toISOString()
3} else if (isPlainObject(val)) {
4 val = JSON.stringify(val)
5}
对于上节课我们对请求参数值的判断,我们也应该用 isPlainObject 才更加合理。
helpers/util.js
代码语言:javascript复制1// export function isObject (val: any): val is Object {
2// return val !== null && typeof val === 'object'
3// }
既然现在 isObject 方法不再使用,我们先将其注释。
实现请求 body 处理逻辑
index.ts:
代码语言:javascript复制 1import { transformRequest } from './helpers/data'
2
3```typescript
4function processConfig (config: AxiosRequestConfig): void {
5 config.url = transformURL(config)
6 config.data = transformRequestData(config)
7}
8
9function transformRequestData (config: AxiosRequestConfig): any {
10 return transformRequest(config.data)
11}
我们定义了 transformRequestData 函数,去转换请求 body 的数据,内部调用了我们刚刚实现的的 transformRequest 方法。
然后我们在 processConfig 内部添加了这段逻辑,在处理完 url 后接着对 config 中的 data 做处理。
编写 demo
代码语言:javascript复制 1axios({
2 method: 'post',
3 url: '/base/post',
4 data: {
5 a: 1,
6 b: 2
7 }
8})
9
10const arr = new Int32Array([21, 31])
11
12axios({
13 method: 'post',
14 url: '/base/buffer',
15 data: arr
16})
我们在 examples/base/app.ts 添加 2 段代码,第一个 post 请求的 data 是一个普通对象,第二个请求的 data 是一个 Int32Array 类型的数据,它是可以直接传给 XMLHttpRequest 对象的 send 方法的。
代码语言:javascript复制 1router.post('/base/post', function(req, res) {
2 res.json(req.body)
3})
4
5router.post('/base/buffer', function(req, res) {
6 let msg = []
7 req.on('data', (chunk) => {
8 if (chunk) {
9 msg.push(chunk)
10 }
11 })
12 req.on('end', () => {
13 let buf = Buffer.concat(msg)
14 res.json(buf.toJSON())
15 })
16})
我们接着在 examples/server.js 中添加 2 个路由,分别针对这俩种请求,返回请求传入的数据。
然后我们打开浏览器运行 demo,看一下结果,我们发现 /base/buffer 的请求是可以拿到数据,但是 base/post 请求的 response 里却返回的是一个空对象,这是什么原因呢?
实际上是因为我们虽然执行 send 方法的时候把普通对象 data 转换成一个 JSON 字符串,但是我们请求header 的 Content-Type 是 text/plain;charset=UTF-8,导致了服务端接受到请求并不能正确解析请求 body 的数据。