我正在参加「掘金·启航计划」
大家好,最近在看axios库的源码时,突发奇想:有没有可能,自己去实现一个类似axios的库呢?
说干就干,我们先来看看axios库实现了哪些功能 github.com/axios/axios
明确需求
通过阅读ReadMe,我们发现以下几点:
- 浏览器端基于
XMLHttpRequest实现的 - nodejs端基于
http实现 - 支持
Promise - 支持请求拦截和响应拦截
- 支持数据转换
- 支持取消请求
- 自动推断
JSON格式数据 - 自动将数据对象序列化为
multipart/form-data和x-www-form-urlencoded主体编码 - 防止
XSRF攻击
制定实现的功能点
因为我们大部分使用的场景是基于浏览器端,基于此,我们实现基于XMLHttpRequest部分的功能。通过对上述的功能点我们小步走
- 创建仓库
- 我们使用ts 基础库的开发脚手架
TypeScript library starter,不过这个库很久没更新了,不过影响功能的实现 - github.com/alexjoverm/…
- 测试用例使用了
nodejs做后端服务,文中没有对这一部分进行详细的描述,可自行实现。
- 我们使用ts 基础库的开发脚手架
基础功能实现
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
我们来实现基础的功能点,定义一个axios方法,最终是通过xhr来实现请求的,具体实现如下:
// types index
export type Method = 'get' | 'GET'
| 'delete' | 'Delete'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
}
// index
import {AxiosRequestConfig} from './types'
import xhr from './xhr'
function axios(config:AxiosRequestConfig){
xhr(config)
}
export default axios
// xhr
export default function xhr(config: AxiosRequestConfig): void {
const { data = null, url, method = 'get' } = config
const request = new XMLHttpRequest()
request.open(method.toUpperCase(), url, true)
request.send(data)
}
至此,我们就实现了简易版的axios请求。
测试
测试用例如下
// test
import axios from './axios'
// should send a GET request to the specified URL
axios({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'get'
})
我们打开浏览器,可以看到有xhr请求,并且返回了数据,说明我们的函数是正确的
处理url带参逻辑
在文档中,我们可以看到如下描述
需要处理功能点:
- 普通对象
- 拼接在
url后面 URLSearchParams对象,相当于encodeURL- 非空处理
// plain object
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
// valid Date
const toString = Object.prototype.toString
export function isDate (val: any): val is Date {
return toString.call(val) === '[object Date]'
}
// encode url
// 将不需要处理的字符转换回来 如:@ ? : [] $
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, ']')
}
// 拼接url
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) {
// 移除Hash
const markIndex = url.indexOf('#')
if (markIndex !== -1) {
url = url.slice(0, markIndex)
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
}
return url
}
- 上述实现的最小的功能点,现在将其接入
axios中
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)
}
测试
axios({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'get',
params: {
id: 1
}
})
axios({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'get',
params: {
id: 1,
userId: 1
}
})
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts',
params: {
foo: '@:$, '
}
})
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts',
params: {
foo: 'bar',
baz: null
}
})
我们打开浏览器控制台,可以看到请求url是正确的。
总结
我们根据axios文档提示,实现了部分简易的逻辑,还有很多的功能点未完成,下一章节中,我们继续去实现基础功能的处理。通过阅读源码和文档的形式,并实现MVP。可以极大的加深我们对源码的认识和提高自己的编程技术,当然,也可以借助GPT等工具,更加容易理解源码。