基于TypeScript封装Axios笔记(二)

时间:2022-07-23
本文章向大家介绍基于TypeScript封装Axios笔记(二),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
处理请求 url 参数

需求分析

还记得我们上节课遗留了一个问题,再来看这个例子:

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 上。

再来看几个更复杂的例子。

参数值为数组

1axios({
2 method: 'get',
3  url: '/base/get',
4  params: {
5    foo: ['bar', 'baz']
6  }
7})

最终请求的 url 是 /base/get?foo[]=bar&foo[]=baz'。

参数值为对象

1axios({
2 method: 'get',
3  url: '/base/get',
4  params: {
5    foo: {
6      bar: 'baz'
7    }
8  }
9})

最终请求的 url 是 /base/get?foo=%7B%22bar%22:%22baz%22%7D,foo 后面拼接的是 {"bar":"baz"} encode 后的结果。

参数值为 Date 类型

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() 的结果。

特殊字符支持

对于字符 @、:、

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 参数中的。

1axios({
2 method: 'get',
3  url: '/base/get',
4  params: {
5    foo: 'bar',
6    baz: null
7  }
8})

最终请求的 url 是 /base/get?foo=bar。

丢弃 url 中的哈希标记

1axios({
2 method: 'get',
3  url: '/base/get#hash',
4  params: {
5    foo: 'bar'
6  }
7})

最终请求的 url 是 /base/get?foo=bar

保留 url 中已存在的参数

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:

 1import { isDate, isObject } from './util'
2
3function encode (val: string): string {
4  return encodeURIComponent(val)
5    .replace(/%40/g, '@')
6    .replace(/%3A/gi, ':')
7    .replace(/%24/g, '$')
8    .replace(/%2C/gi, ',')
9    .replace(/%20/g, '+')
10    .replace(/%5B/gi, '[')
11    .replace(/%5D/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:

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 文件中添加如下代码:

 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:

 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 作为入口文件:

 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。‍

但是我们最常用的场景还是传一个普通对象给服务端,例如:

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:

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:

1if (isDate(val)) {
2 val = val.toISOString()
3} else if (isPlainObject(val)) {
4 val = JSON.stringify(val)
5}

对于上节课我们对请求参数值的判断,我们也应该用 isPlainObject 才更加合理。

helpers/util.js

1// export function isObject (val: any): val is Object {
2// return val !== null && typeof val === 'object'
3// }

既然现在 isObject 方法不再使用,我们先将其注释。

实现请求 body 处理逻辑

index.ts:

 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

 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 方法的。

 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 的数据。‍