目录

🌒 实现处理请求 URL 参数

# 需求分析

根据不同的 URL params 参数有不同的情况分析:

  • 普通参数:

    axios({
      method: 'get',
      url: '/base/get',
      params: {
        a: 1,
        b: 2
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8

    最终请求 URL 为: /base/get?a=1&b=2 ,这样服务器就可以通过请求 URL 解析到传送的参数数据。实际上就是把 params 对象中的 keyvalue 拼接到 URL 上。

  • 参数值为数组时:

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

    最终请求 URL 为: /base/get?foo[]=bar&foo[]=baz

  • 参数值为对象时:

    const date = new Date()
    
    axios({
      method: 'get',
      url: '/base/get',
      params: {
        foo: {
          bar: 'baz'
        }
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

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

  • 参数值为 Date 类型:

    const date = new Date()
    
    axios({
      method: 'get',
      url: '/base/get',
      params: {
        date
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    最终请求的 url/base/get?date=2019-04-01T05:55:39.030Zdate 后面拼接的是 date.toISOString() 的结果。

  • 支持特殊字符:允许特殊字符出现在 URL 中,不希望被 encode

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

    最终请求 URL 为 /base/get?foo=@:$+ ,注意,我们会把空格 转换成 +

  • 支持空值的忽略:对于值为 null 或者 undefined 的属性,不会添加到 url 参数中的。

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

    最终请求 URL 为: /base/get?foo=bar

  • 丢弃 URL 中的哈希标记:

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

    最终请求 URL 为: /base/get?foo=bar

  • 保留 URL 中已经存在的参数:

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

    最终请求 URL 为: /base/get?foo=bar&bar=baz

# buildURL 函数实现

根据需求分析,需要实现一个工具函数,将 params 中的数据拼接到 URL 上。

  • 创建 helpers 目录,在目录下创建 url.ts 编写处理 URL 相关的工具。
  • 在同目录下创建 util.ts 存放相关的工具;

helpers/util.ts :检验数据的类型,是否为 Date 对象,或者是普通对象 object

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'
}
1
2
3
4
5
6
7
8
9

helpers/url.ts

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 buildURL(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
}
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
  • encode() 函数处理特殊字符;
  • buildURL 拼接带有参数的 URL。首先 先遍历 params 对象中的键值,判断其中的类型,分别按照不同的类型(数组、 日期、普通对象)编码,然后再添加到一个处理后的参数字符串数组中;使用 & 拼接这个字符串数组生成序列化的参数字符串;接着处理哈希标记,并且如果 URL 中已经存在参数,要在其后添加 & 再加入字符串处理后的参数。

# 在入口文件引入

import { AxiosRequestConfig } from './types'
import xhr from './xhr'
import { buildURL } from './helpers/url'

function axios(config: AxiosRequestConfig) {
  processConfig(config)
  xhr(config)
}

function processConfig(config: AxiosRequestConfig) {
  config.url = transformURL(config)
}

function transformURL (config: AxiosRequestConfig) {
  const {url, params} = config
  return buildURL(url, params)
}

export default axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在执行 xhr 函数之前,添加处理 config 的方法 processConfig 。在 processConfig 内部,执行 transformURL 处理 URL。

# 编写 DEMO 测试

exampls 目录下创建 base 目录:

base/index.html

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <title>simple example</title>
</head>
<body>

<script src="/__build__/simple.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11

base/app.ts

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'
  }
})
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

server.js 中添加 router

router.get('/base/get', function(req, res) {
  res.json(req.query)
})
1
2
3

examples/index.html 中添加测试链接:

<li><a href='base'>Base</a></li>
1

最后进入链接之后,可以看到发送了多个请求,查看请求的 URL 和响应的信息。

📢 上次更新: 2022/09/02, 10:18:16