TS + NodeJS实现axios

TS + NodeJS 实现 axios

1
git clone https://github.com/alexjoverm/typescript-library-starter.git ts-axios

需求

在浏览器端使用 XMLHttpRequest 对象通讯

Promise API

请求响应拦截器

请求数据和响应数据转换

请求的取消

JSON 数据的自动转换

客户端防止 XSRF

框架工具

image-20250129205459495 image-20250129205721267 image-20250129205345604

请求代码

axios 最基本的操作

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

创建入口文件

index.ts

1
2
3
function axios(config) {}

export default axios

xhr.ts

xhr 函数用于发送 HTTP 请求的工具函数,基于 XMLHttpRequest API 实现,并提供了一个简单的接口来配置和发送请求。

1
2
3
4
5
6
7
8
import { AxiosRequestConfig } from './types'

export default function xhr(config: AxiosRequestConfig) {
const { data = null, url, method = 'get' } = config // 解构设置默认值
const request = new XMLHttpRequest() // 实例化一个 XMLHttpRequest 对象 并赋值给 request 变量
request.open(method.toUpperCase(), url, true) // 调用 open 方法 并传入 method url true 三个参数 并将 method 转换为大写并赋值给 method,url. async:true
request.send(data) // 调用 send 方法 并传入 data 参数 并赋值给 request 变量
}

安装依赖

image-20250130164413743

1

webpack 配置

webpack.config.js

整体导出一个对象,包含 mode(开发模式),entry,output,module,resolve,plugins 几个关键属性

entry

1
2
3
4
5
6
7
8
9
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const fullDir = path.join(__dirname, dir)
const entry = path.join(fullDir, 'app.ts')
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
entries[dir] = ['webpack-hot-middleware/client', entry]
}

return entries
}, {}),

entries 收集了多个目录的入口文件 (app.ts) ,并将每个入口引入一个用于热更新的文件

entries 是一个对象,key 为目录名

output

将 entries 的 key 打包成 filename

1
2
3
4
5
output: {
path: path.join(__dirname, '__build__'),
filename: '[name].js',
publicPath: '/__build__/'
},

module

rules 定义一系列处理不同类型文件的规则

.ts :tslint-loader 进行预处理 (包括类型检查)

.tsx :transpileOnly: true 只进行编译而不进行类型检查

.css :style-loader 将 CSS 注入到 DOM 中,css-loader 处理 CSS 文件中的 @importurl()

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
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: [
{
loader: 'tslint-loader'
}
]
},
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},

resolve

指定在导入模块时可以省略的文件扩展名,Webpack 会自动尝试这些扩展名。

1
2
3
resolve: {
extensions: ['.ts', '.tsx', '.js']
}

plugins

热更新 | 发生错误不打包文件

1
2
3
4
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]

核心功能

处理 url 参数(params)

核心思想:把 params 的 key&value 解构拼接到 URL 上

image-20250210214938544

helpers/url.ts

解构 URL -> params

1
2
3
4
export function bulidURL(url: string, params?: any): string {//解构
...
return url
}

需处理逻辑

没有参数

image-20250210215202014
1
2
3
4
if (!params) {
// 没有参数 直接返回 url
return url
}

解构 params -> key/value

params 是一个包含请求参数的对象

1
2
3
4
5
6
//例
const params = {
name: 'John',
age: 30,
hobbies: ['reading', 'coding'],
}

具体代码

1
2
3
4
5
6
const parts: string[] = [] // 存放参数的数组

Object.keys(params).forEach(key => { // key 依次取到 'name', 'age', 'hobbies' 等值
const val = params[key] // 对象key值所对应的value
...
})

value 为 null 或 undefined

1
2
3
if (val === null || typeof val === 'undefined') {
return
}

value数组

image-20250210214959177
1
2
3
4
5
6
7
8
9
let values = [] // 定义 values 变量

if (Array.isArray(val)) {
// 如果 value 为数组 直接赋值给 values
values = val
key += '[]' // 键名后面添加一个 []
} else {
values = [val] // 赋值给 values
}

valueData | 字符串 | 对象

image-20250210215112025 image-20250210215135428 image-20250210215027145

helpers/util.ts

1
2
3
4
5
6
7
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
10
11
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, ']')
}
1
2
3
4
5
6
7
8
9
10
11
values.forEach((val) => {
// 遍历 values
if (isDate(val)) {
// value 为日期
val = val.toISOString() // 转换为 ISO 格式赋值给 val
} else if (isObject(val)) {
// value 为对象
val = JSON.stringify(val) // 转换为字符串赋值给 val
}
parts.push(`${encode(key)}=${encode(val)}`) // 特殊字符转码赋值给 parts
})

拼装成 URL

parts.join(‘&’) 效果

1
;['name=John', 'age=30', 'hobbies[]=reading', 'hobbies[]=coding']
1
'name=John&age=30&hobbies[]=reading&hobbies[]=coding'
image-20250210215213939 image-20250210215227134
1
2
3
4
5
6
7
8
9
let serializedParams = parts.join('&') // 将 parts 数组中的所有元素连接成一个字符串,并使用 & 作为分隔符
if (serializedParams) {
// 如果 serializedParams 不为空 则拼接 url
const markIndex = url.indexOf('#') // 查找 URL 中是否存在 # 符号 返回 # 符号在 URL 中的位置索引
if (markIndex !== -1) {
url = url.slice(0, markIndex) // 使用slice方法将URL截取到哈希部分之前,从而去除哈希部分
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams // 确保URL中的查询参数?存在
}

处理 body 数据

helpers/util.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'
// }

export function isPlainObject(val: any): val is Object {
// 判断是否为普通对象
return toString.call(val) === '[object Object]'
}

helpers/data.ts

1
2
3
4
5
6
7
8
import { isPlainObject } from './util'

export function transformRequest(data: any): any {
if (isPlainObject(data)) {
return JSON.stringify(data)
}
return data
}

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.data = transformRequestData(config)
}

function transformURL(config: AxiosRequestConfig): string {
// 处理 url 返回拼接后的 url
const { url, params } = config
return bulidURL(url, params)
}

function transformRequestData(config: AxiosRequestConfig): void {
// 处理 data
config.data = transformRequest(config.data)
}

处理 header

headers 一般传递诸如认证信息、内容类型等元数据给服务器

image-20250212030911535

helpers/header.ts

处理 headers

1
2
3
4
5
6
7
8
9
10
11
export function processHeaders(headers: any, data: any): any {
// processHeaders 函数接收 headers 和 data 两个参数
normalizeHeaderName(headers, 'Content-Type') // 调用 normalizeHeaderName 函数将 headers 的 Content-Type 属性名规范化
if (isPlainObject(data)) {
// 如果 data 是一个普通对象
if (headers && !headers['Content-Type']) {
// 如果 headers 不存在 Content-Type 属性
headers['Content-Type'] = 'application/json;charset=utf-8' // 设置 Content-Type 属性为 application/json;charset=utf-8
}
}
}

Content-Type 是 HTTP 请求头的一部分,用于指示实体主体(即请求或响应的正文部分)的数据类型。它告诉接收方如何解析接收到的数据。例如,当发送 JSON 格式的数据时,通常会将 Content-Type 设置为 application/json,这样接收方就知道应该以 JSON 的形式来解析数据

辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function normalizeHeaderName(headers: any, normalizedName: string): void {
// normalizeHeaderName 函数接收 headers 和 normalizedName 两个参数
if (!headers) {
// 如果 headers 不存在
return // 直接返回
}
Object.keys(headers).forEach((name) => {
// 遍历 headers 的所有属性
if (
name !== normalizedName &&
name.toUpperCase() === normalizedName.toUpperCase()
) {
// 如果属性名不等于 normalizedName 并且属性名的大写形式等于 normalizedName 的大写形式
headers[normalizedName] = headers[name] // 将 headers 的属性名设置为 normalizedName (content-type -> Content-Type)
delete headers[name] // 删除 headers 的属性名
}
})
return headers // 返回 headers
}

types/index.ts 加入 headers

1
2
3
4
5
6
7
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
headers?: any
}

index.ts 增添处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.headers = transformHeaders(config) // 先处理 headers
config.data = transformRequestData(config) // 再处理 data
}

function transformHeaders(config: AxiosRequestConfig): any {
// 处理 headers 调用 processHeaders 方法
const { headers = {}, data } = config
return processHeaders(headers, data)
}

xhr.ts 添加 header 处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default function xhr(config: AxiosRequestConfig): void {
const { data = null, url, method = 'get', headers } = config // 添加header解构
const request = new XMLHttpRequest() // 实例化一个 XMLHttpRequest 对象 并赋值给 request 变量
request.open(method.toUpperCase(), url, true)

Object.keys(headers).forEach((name) => {
// 遍历 headers 的所有属性
if (data === null && name.toLowerCase() === 'content-type') {
// 如果 data 为 null 并且属性名为 content-type
delete headers[name] // 删除 headers 的属性名
} else {
request.setRequestHeader(name, headers[name]) // 调用 setRequestHeader 方法 并传入 name headers[name] 两个参数 并赋值给 request 变量
}
})

request.send(data) // 调用 send 方法 并传入 data 参数 并赋值给 request 变量
}

总结

这部分处理实现了

为 Content-Type 自动添加 application/json;charset=UTF-8(告知服务端解析的是 JSON 数据)

获取处理 response

types / index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export interface AxiosRequestConfig {
//config接口
url: string
method?: Method
data?: any
params?: any
headers?: any
responseType?: XMLHttpRequestResponseType //新增
}

export interface AxiosResponse {
//response接口
data: any
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request: any
}

export interface AxiosPromise extends Promise<AxiosResponse> {
//promise接口
}

xhr.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
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types'

export default function xhr(config: AxiosRequestConfig): AxiosPromise {// xhr 函数接收一个 config 参数
return new Promise((resolve, reject) => // 返回一个 Promise 对象 并传入 resolve reject 两个参数
const { data = null, url, method = 'get', headers, responseType } = config // 解构赋值拿到变量
const request = new XMLHttpRequest() // 实例化一个 XMLHttpRequest 对象 并赋值给 request 变量

if (responseType) {
request.responseType = responseType
}

request.open(method.toUpperCase(), url, true) // 调用 open 方法 并传入 method url true 三个参数 并将 method 转换为大写并赋值给 method,url. async:true异步

request.onreadystatechange = function handleLoad() {// 调用 onreadystatechange 方法 并传入 handleLoad 函数 并赋值给 request 变量
if (request.readyState !== 4) {// 4状态为正确接收到数据 (// 没有收到正确的响应)
return
}
const responseHeaders = request.getAllResponseHeaders() // 调用 getAllResponseHeaders 方法 并赋值给 responseHeaders 变量(后面要处理这个字符串)
const responseData = responseType && responseType !== 'text' ? request.response : request.responseText // 调用 responseText 方法 并赋值给 responseData 变量
const response: AxiosResponse = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
}
resolve(response) // 调用 resolve 方法 并传入 response 参数
}

Object.keys(headers).forEach(name => {// 遍历 headers 的所有属性
if (data === null && name.toLowerCase() === 'content-type') {// 如果 data 为 null 并且属性名为 content-type
delete headers[name] // 删除 headers 的属性名
} else {
request.setRequestHeader(name, headers[name]) // 调用 setRequestHeader 方法 并传入 name headers[name] 两个参数 并赋值给 request 变量
}
})

request.send(data) // 调用 send 方法 并传入 data 参数 并赋值给 request 变量
})
}

index.ts

1
2
3
4
5
6
7
import { AxiosRequestConfig, AxiosPromise } from './types'

function axios(config: AxiosRequestConfig): AxiosPromise {
// axios 函数接收一个 config 参数
processConfig(config)
return xhr(config)
}

getAllResponseHeaders方法处理

XMLHttpRequest 对象的 getAllResponseHeaders 方法获取到的值是如下字符串

1
2
3
4
5
6
date:Fri,05 Apr 2019 12:40:49 GMT
etag: W/"d-Ssxx4FRxEutDLwo2+xkkxKc4y0k''
connection: keep-alive
x-powered-by: Express
content-length:13
content-type:application/json;charset=utf-8

每一行都是以回车和换行符\r\n结束,是每个 header 属性的分隔符

而我们希望解析成的对象结构

1
2
3
4
5
6
7
8
{
date:Fri,05 Apr 2019 12:40:49 GMT
etag: W/"d-Ssxx4FRxEutDLwo2+xkkxKc4y0k'',
connection: keep-alive,
x-powered-by: Express,
content-length:13
content-type:application/json;charset=utf-8
}

headers.ts 新增 parseHeaders 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export function parseHeaders(headers: string): any {
// parseHeaders 函数接收 headers 参数
let parsed = Object.create(null) // 创建一个空对象
if (!headers) {
// 如果 headers 不存在
return parsed // 返回 parsed
}
headers.split('\r\n').forEach((line) => {
// 遍历 headers 的每一行
let [key, val] = line.split(':') // 分割每一行的 key 和 val
key = key.trim().toLowerCase() // 去掉 key 的空格并将 key 转换为小写
if (!key) {
// 如果 key 不存在
return // 直接返回
}
if (val) {
// 如果 val 存在
val = val.trim() // 去掉 val 的空格
}
parsed[key] = val // 将 key 和 val 赋值给 parsed
})
return parsed // 返回 parsed
}

处理响应 data

不设置 responseType 情况下,服务端返回数据是字符串类型

1
data: "{"a":1,"b":2}"

我们要转成对象

1
2
3
4
data:{
a: 1,
b: 2
}

data.ts 新增 transformResponse 转化函数

1
2
3
4
5
6
7
8
9
10
export function transformResponse(data: any): any {
if (typeof data === 'string') {
try {
data = JSON.parse(data)
} catch (e) {
// do nothing
}
}
return data
}

index.ts 处理 axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { transformRequest, transformResponse } from './helpers/data' // 新增引入转化函数

function axios(config: AxiosRequestConfig): AxiosPromise {
processConfig(config)
return xhr(config).then((res) => {
//请求结束后对 response data 进行处理
return transformResponseData(res)
})
}

function transformResponseData(res: AxiosResponse): AxiosResponse {
// 处理 response data
res.data = transformResponse(res.data)
return res
}
1
/// 基础核心功能至此已全部实现 ///

错误处理(reject 拒回)

处理网络错误

网络不通时发送请求会触发 XMLHttpRequest 对象实例的 error 事件,所以我们可以在 onerror 事件回调函数中捕获此类错误

xhr.ts 中添加

1
2
3
4
request.onerror = function handleError() {
// 调用 onerror 方法 并传入 handleError 函数 并赋值给 request 变量
reject(new Error('Network Error')) // 调用 reject 方法 并传入 Network Error 参数
}

处理超时错误

设置一个 timeout,当请求超过某个时间后没收到响应则终止,并触发 timeout 事件

默认超时时间为 0(永不超时,我们首先需要允许程序可配置超时时间

type / index.ts

1
2
3
4
5
6
7
8
9
10
export interface AxiosRequestConfig {
//config接口
url: string
method?: Method
data?: any
params?: any
headers?: any
responseType?: XMLHttpRequestResponseType
timeout?: number // 新增timeout
}

xhr.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const {
data = null,
url,
method = 'get',
headers,
responseType,
timeout,
} = config // 新增解构

// 新增逻辑
if (timeout) {
// 如果 timeout 存在
request.timeout = timeout
}

request.ontimeout = function handleTimeout() {
reject(new Error(`Timeout of ${timeout} ms exceeded`)) // 超过 ${timeout} 毫秒 超时
}

状态码处理(!200 错误)

处理时机:接收到响应的时候

xhr.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
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
// 没有收到正确的响应
return
}

//也就是在这里//
if (request.status === 0) {
// 如果发送网络错误 || 超时错误
return
}

resolve(response) //
}

function handleResponse(response: AxiosResponse): void {
// 传入处理 response 参数
if (response.status >= 200 && response.status < 300) {
// 状态码 大于等于 200 并且小于 300
resolve(response) // 调用 resolve 方法 并传入 response 参数
} else {
reject(new Error(`Request failed with status code ${response.status}`)) // 报错状态码
}
}

错误信息增强

按照之前的操作,我们实现了对基础错误信息的处理,但我们对外提供的错误信息是十分有限的

我们希望对外提供的错误信息包括

错误文本信息(报错显示)

请求对象配置 config

错误代码 code

XMLHttpRequest 对象实例 request

自定义响应对象 response

创建 AxiosError 类

types / index.ts 定义接口

1
2
3
4
5
6
7
8
export interface AxiosError extends Error {
//error接口
isAxiosError: boolean
config: AxiosRequestConfig
code?: string
request?: any
response?: AxiosResponse
}

新建 helpers / error.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
import { AxiosRequestConfig, AxiosResponse } from '../types'

export class AxiosError extends Error {
// 定义一个AxiosError类继承Error类 实现AxiosError接口 并设置 isAxiosError为true
isAxiosError: boolean
config: AxiosRequestConfig
code?: string | null
request?: any
response?: AxiosResponse

constructor(
//构造函数 接收五个参数赋值给对应的属性 并设置 isAxiosError为true
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
) {
super(message) //调用父类的构造函数 传入message参数赋值给message属性

this.config = config
this.code = code
this.request = request
this.response = response
this.isAxiosError = true

Object.setPrototypeOf(this, AxiosError.prototype) // 修复原型链问题
}
}

export function createError( //定义一个createError函数 接收五个参数 并返回一个AxiosError对象
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
) {
const error = new AxiosError(message, config, code, request, response)

return error
}

createError 方法应用

对错误对象创建的逻辑进行修改

xhr.ts

onerror 修改

1
2
3
request.onerror = function handleError() {
reject(new Error('Network Error')) // 调用 reject 方法 并传入 Network Error 参数
}

替换为

1
2
3
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request)) // 传入 'Network Error',config,null,request 参数(事件触发时拿不到response就不传了) 调用 createError 方法并传入参数
}

ontimeout 修改

1
2
3
4
request.ontimeout = function handleTimeout() {
// 调用 ontimeout 方法 并传入 handleTimeout 函数 并赋值给 request 变量
reject(new Error(`Timeout of ${timeout} ms exceeded`)) // 调用 reject 方法 并传入 Timeout of ${timeout} ms exceeded 参数
}

替换为

1
2
3
4
5
6
7
8
9
10
11
request.ontimeout = function handleTimeout() {
// 调用 ontimeout 方法 并传入 handleTimeout 函数 并赋值给 request 变量
reject(
createError(
`Timeout of ${timeout} ms exceeded`,
config,
'ECONNABORTED',
request
)
) // 传入 `Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request 参数 调用 createError 方法 并传入参数
}

handleResponse 修改

1
2
3
4
5
6
7
8
9
function handleResponse(response: AxiosResponse): void {
// 传入处理 response 参数
if (response.status >= 200 && response.status < 300) {
// 状态码 大于等于 200 并且小于 300
resolve(response) // 调用 resolve 方法 并传入 response 参数
} else {
reject(new Error(`Request failed with status code ${response.status}`)) // 调用 reject 方法 并传入 Request failed with status code ${response.status} 参数
}
}

替换为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function handleResponse(response: AxiosResponse): void {
// 传入处理 response 参数
if (response.status >= 200 && response.status < 300) {
// 状态码 大于等于 200 并且小于 300
resolve(response) // 调用 resolve 方法 并传入 response 参数
} else {
reject(
createError(
`Request failed with status code ${response.status}`,
config,
null,
request,
response
)
) // 传入 `Request failed with status code ${response.status}`,config,null,request,response 参数 调用 createError 方法 并传入参数
}
}

接口扩展

需求分析

为了用户更加方便地使用 axios 发送请求,我们可以为所有支持的请求方法扩展一些接口

1
2
3
4
5
6
7
8
9
//[可选项]
axios.request (config)
axios.get (url[,config])
axios.delete (url[,config])
axios.head (url[,config])
axios.options (url[,config])
axios.post (url[,datal,config])
axios.put (url[,datal,config])
axios.patch (url[,datal,config])

types / index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export interface Axios {
//axios接口
request(config: AxiosRequestConfig): AxiosPromise
get(url: string, config?: AxiosRequestConfig): AxiosPromise
delete(url: string, config?: AxiosRequestConfig): AxiosPromise
head(url: string, config?: AxiosRequestConfig): AxiosPromise
options(url: string, config?: AxiosRequestConfig): AxiosPromise
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}

export interface AxiosInstance extends Axios {
//axios实例接口 既有函数类型又有属性接口
(config: AxiosRequestConfig): AxiosPromise
}

创建 Axios.ts

存放一些发送请求核心代码

core / Axios.ts //大写表示是一个类

1
2
3
4
5
6
7
import { AxiosRequestConfig, AxiosPromise } from '../types'

export default class Axios {
request(config: AxiosRequestConfig): AxiosPromise {
// 实现request方法
}
}

core / dispatchRequest.ts

封装 axios.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
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from '../types'
import xhr from '../xhr'
import { bulidURL } from '../helpers/url'
import { transformRequest, transformResponse } from '../helpers/data'
import { processHeaders } from '../helpers/header'

export default function dispatchRequest(
config: AxiosRequestConfig
): AxiosPromise {
// dispatchRequest 函数接收一个 config 参数
processConfig(config)
return xhr(config).then((res) => {
//请求结束后对 response data 进行处理
return transformResponseData(res)
})
}

function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.headers = transformHeaders(config) // 先处理 headers
config.data = transformRequestData(config) // 再处理 data
}

function transformURL(config: AxiosRequestConfig): string {
// 处理 url 返回拼接后的 url
const { url, params } = config
return bulidURL(url, params)
}

function transformRequestData(config: AxiosRequestConfig): void {
// 处理 data 调用 transformRequest 方法
config.data = transformRequest(config.data)
}

function transformHeaders(config: AxiosRequestConfig): any {
// 处理 headers 调用 processHeaders 方法
const { headers = {}, data } = config
return processHeaders(headers, data)
}

function transformResponseData(res: AxiosResponse): AxiosResponse {
// 处理 response data
res.data = transformResponse(res.data)
return res
}

然后将 xhr.ts 移入 core 文件夹(xhr 也作为核心模块)

部分报错修改 (严格模式)

1
2
3
4
5
//dispatchRequest.ts
return bulidURL(url!, params)

//xht.ts
request.open(method.toUpperCase(), url!, true) // 调用 open 方法传入 method url true 三个参数 并将 method 转换为大写并赋值给 method,url. async:true异步

Tips: 断言( ! )

TS 中,当你保证某个变量不会为 null 或者 undefined,但编译器却无法推断出来的时候,就可以使用非空断言操作符 ( ! ),不会在运行时进行检查,但要是你做出了错误的保证,在运行时就可能会出现错误。

实现 Axios 类中接口

Axios.ts

1
2
3
4
5
6
7
import { AxiosRequestConfig, AxiosPromise } from '../types'
import dispatchRequest from './dispatchRequest'

export default class Axios {
request(...)
get(...)
}

实现 request 接口

1
2
3
request(config: AxiosRequestConfig): AxiosPromise {// 传入config参数 并返回AxiosPromise
return dispatchRequest(config)
}

逻辑封装

1
2
3
4
5
6
7
8
_requestMethodWithoutData(
method: Method,
url: string,
config?: AxiosRequestConfig
): AxiosPromise {
// 封装_requestMethodWithoutData方法传入method,url和config参数 并返回AxiosPromise
return this.request(Object.assign(config || {}, { method, url })) // 调用request方法 并传入一个对象
}

实现 get 接口

1
2
3
get(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url和config参数
return this._requestMethodWithoutData('get', url, config) // 调用方法 传入一个对象
}

实现 delete 接口

1
2
3
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url和config参数
return this._requestMethodWithoutData('delete', url, config) // 调用方法 传入一个对象
}

实现 head 接口

1
2
3
head(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url和config参数
return this._requestMethodWithoutData('head', url, config) // 调用方法 并传入一个对象
}

实现 options 接口

1
2
3
options(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url和config参数
return this._requestMethodWithoutData('options', url, config) // 调用方法 并传入一个对象
}

逻辑封装

1
2
3
4
5
6
7
8
9
_requestMethodWithData(
method: Method,
url: string,
data?: any,
config?: AxiosRequestConfig
): AxiosPromise {
// 封装_requestMethodWithData方法传入method,url,data和config参数 并返回AxiosPromise
return this.request(Object.assign(config || {}, { method, url, data })) // 调用request方法 并传入一个对象
}

实现 post 接口

1
2
3
head(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url,data和config参数
return this._requestMethodWithData('post', url, data, config) // 调用方法 并传入一个对象
}

实现 put 接口

1
2
3
put(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url,data和config参数
return this._requestMethodWithData('put', url, data, config) // 调用方法 并传入一个对象
}

实现 patch 接口

1
2
3
patch(url: string, config?: AxiosRequestConfig): AxiosPromise {// 传入url,data和config参数
return this._requestMethodWithData('patch', url, data, config) // 调用方法 并传入一个对象
}

混合对象实现

首先这个对象是函数,其次这个对象包括 Axios 类内所有原型属性和实例属性

helpers / utils

extend 合成函数

1
2
3
4
5
// 示例
const target = { a: 1 }
const source = { b: 2 }
const result = extend(target, source)
console.log(result) // 输出: { a: 1, b: 2 }

这里我们使用**泛型** TU

T 代表目标对象 to 的类型,U 代表源对象 from 的类型

函数的返回值类型是 T & U,也就是 tofrom 两个对象类型的交集。

1
2
3
4
5
6
7
export function extend<T, U>(to: T, from: U): T & U {
// 合并对象
for (const key in from) {
;(to as T & U)[key] = from[key] as any //把 from 对象的属性值赋值给 to 对象对应的属性。这里运用了类型断言 as T & U 和 as any,让 TypeScript 编译器认可这种赋值操作。
}
return to as T & U //也用了类型断言 as T & U
}

工厂函数实现

处理 axios.ts

删掉内容,只保留 export default axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { AxiosInstance } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'

function createInstance(): AxiosInstance {
const context = new Axios() //实例化一个 Axios 对象 并赋值给 context 变量
const instance = Axios.prototype.request.bind(context) //调用 request 方法 并传入 context 参数 并赋值给 instance 变量 绑定 this 指向 context

extend(instance, context) //调用 extend 方法 并传入 instance 和 context 参数 实现 instance 对象继承 context 对象
return instance as AxiosInstance //返回 instance 对象 并断言为 AxiosInstance 类型
}

const axios = createInstance() //调用 createInstance 方法 并赋值给 axios 变量

export default axios

axios 函数重载

目前我们函数只能存放一个对象

1
2
3
4
5
6
7
axios({
url: '/extend/post',
method: 'post',
data:{
msg:'hi'
}
})

那么如果 axios 同时含有两个参数

1
2
3
4
5
6
axios('/extend/post',{
method: 'post',
data:{
msg:'hi'
}
})

type / index.ts

1
2
3
4
export interface AxiosInstance extends Axios {//axios实例接口 既有函数类型又有属性接口
(config: AxiosRequestConfig): AxiosPromis) //(config单参)
(url: string, config?: AxiosRequestConfig): AxiosPromise //添加:函数重载(url+config双参)
}

core / Axios.ts

修改 request 方法

1
2
3
request(config: AxiosRequestConfig): AxiosPromise {// 实现request方法传入config参数
return dispatchRequest(config)
}

改为 👇

1
2
3
4
5
6
7
8
9
10
11
request(url: any, config: any): AxiosPromise {// 实现request方法传入url?config?参数
if (typeof url === 'string') { //url+config双参
if (!config) {
config = {}
}
config.url = url
} else { //config单参
config = url //此时的url就是config
}
return dispatchRequest(config)
}

响应数据支持泛型

通常情况,我们会将后端返回数据格式单独放入一个接口中

设置泛型,提高类型灵活性

type / index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export interface AxiosResponse<T = any> { //response接口
data: T
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request: any
}

export interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> { //promise接口
}

export interface Axios { //axios接口
request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
......
}

export interface AxiosInstance extends Axios { //axios实例接口 既有函数类型又有属性接口
<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T> //函数重载
}

拦截器实现

也就是 axios 二次封装中常见的请求拦截器 & 响应拦截器

我们先回顾一下用户 asios 拦截器是怎么写的

1
2
3
4
5
6
7
8
9
10
11
12
13
import axios from 'axios'

const instance = axios.create({
baseURL:'/api
})
instance.interceptors.request.use(res =>{//请求拦截器
console.log('res',res)
})
instance.interceptors.response.use(res =>{//响应拦截器
console.log('res',res)
})

export default instance

Tip:request 是后添加的先执行,response 是先添加的先执行

拦截器管理类

types / index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export interface AxiosInterceptorManager<T> {
//拦截器接口
use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number //添加拦截器 返回拦截器id
eject(id: number): void //删除拦截器 返回拦截器id
}

export interface ResolvedFn<T = any> {
//成功拦截器接口
(val: T): T | Promise<T>
}

export interface RejectedFn {
//失败拦截器接口
(error: any): any
}

core / interceptorManager.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
import { ResolvedFn, RejectedFn } from '../types'

interface Interceptor<T> {
// 拦截器接口
resolved: ResolvedFn<T>
rejected?: RejectedFn
}

export default class InterceptorManager<T> {
// 拦截器管理类
private interceptors: Array<Interceptor<T>> // 拦截器数组存放的是拦截器对象 每个对象有两个属性 resolved rejected
constructor() {
// 构造函数 初始化拦截器数组
this.interceptors = [] // 拦截器数组中存放的是拦截器对象
}

use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number {
// 添加拦截器
this.interceptors.push({
// 将拦截器对象添加到拦截器数组中
resolved,
rejected,
})
return this.interceptors.length - 1 // 返回拦截器的id 用于删除拦截器
}

forEach(fn: (interceptor: Interceptor<T>) => void): void {
// 遍历拦截器
this.interceptors.forEach((interceptor) => {
// 遍历拦截器数组
if (interceptor !== null) {
// 判断拦截器是否存在
fn(interceptor) // 存在则调用fn函数
}
})
}

eject(id: number): void {
// 删除拦截器 通过id删除拦截器
if (this.interceptors[id]) {
// 判断拦截器是否存在
this.interceptors[id] = null // 存在则将拦截器置为null
}
}
}

链式调用实现

core / Axios.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { AxiosRequestConfig, AxiosPromise,AxiosResponse, Method } from '../types'
import InterceptorManager from './interceptorManager'

interface Interceptors {
request: InterceptorManager<AxiosRequestConfig>
response: InterceptorManager<AxiosResponse>
}

export default class Axios {
interceptors: Interceptors
constructor() {
this.interceptors = { //用户可以通过axios.interceptors.request.use()添加拦截器
request: new InterceptorManager<AxiosRequestConfig>(),
response: new InterceptorManager<AxiosResponse>()
}
}
request(...)...
...
}

完善 request (实现调用链)

image-20250218152615640

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
import { AxiosRequestConfig, AxiosPromise, AxiosResponse, Method,ResolvedFn,RejectedFn } from '../types'

interface PromiseChain<T> {
resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise)//resolved是一个函数类型 接收一个AxiosRequestConfig类型的参数 返回一个AxiosPromise类型的参数
rejected?: RejectedFn //rejected是一个函数类型 接收一个AxiosRequestConfig类型的参数 返回一个AxiosPromise类型的参数
}
...
request(url: any, config: any): AxiosPromise {// 实现request方法传入参数
if (typeof url === 'string') {
if (!config) {
config = {}
}
config.url = url
} else {
config = url
}

const chain:PromiseChain<any>[] = [
{// 创建一个数组chain
resolved: dispatchRequest,
rejected: undefined
}
]
this.interceptors.request.forEach(interceptor => {// 遍历request拦截器
chain.unshift(interceptor) // 将拦截器添加到chain数组的开头
})
this.interceptors.response.forEach(interceptor => {// 遍历response拦截器
chain.push(interceptor) // 将拦截器添加到chain数组的末尾
})
let promise = Promise.resolve(config) // 创建一个promise对象 并传入config参数
while (chain.length) {
// 遍历chain数组
const { resolved, rejected } = chain.shift()! // 取出chain数组的第一个元素
promise = promise.then(resolved, rejected) // 将promise对象的then方法传入resolved和rejected参数
}

return dispatchRequest(config)
}

合并用户配置

用户在发送请求时可以传入一个配置来决定请求的不同行为

甚至可以直接修改一些默认配置 (defaults)

1
2
3
4
axios.defaults.headers.common['test'] = 123 //默认对任何类型的请求的header都添加test属性
axios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded' //默认对post请求的header都添加Content-Type属性
axios.defaults.timeout = 2000

默认配置设置

defaults.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
import { AxiosRequestConfig } from './types'

const defaults: AxiosRequestConfig = {
url: '',
method: 'get',
timeout: 0,
headers: {
common: {
Accept: 'application/json, text/plain, */*', //通用的请求头 接受的响应类型为json/plain文本*/*表示任意类型 优先级最低
},
},
}

const methodsNoData = ['delete', 'get', 'head', 'options']

methodsNoData.forEach((method) => {
defaults.headers[method] = {} //没有数据的请求头 优先级高于通用的请求头
})

const methodsWithData = ['post', 'put', 'patch']

methodsWithData.forEach((method) => {
defaults.headers[method] = {
'Content-Type': 'application/x-www-form-urlencoded', //表单提交的请求头 优先级高于通用的请求头
}
})

export default defaults

core / Axios.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default class Axios {
defaults: AxiosRequestConfig
interceptors: Interceptors

constructor(initConfig?: AxiosRequestConfig) {
this.defaults = initConfig // 初始化defaults属性
this.interceptors = {
//用户可以通过axios.interceptors.request.use()添加拦截器
request: new InterceptorManager<AxiosRequestConfig>(),
response: new InterceptorManager<AxiosResponse>()
}
}
...
}

types / index.ts

1
2
3
4
5
6
7
8
export interface Axios {
defaults: AxiosRequestConfig //默认配置
interceptors: { //拦截器
request: AxiosInterceptorManager<AxiosRequestConfig>
response: AxiosInterceptorManager<AxiosResponse>
}
...
}

重写 axios.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { AxiosInstance, AxiosRequestConfig } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
import defaults from './defaults'

function createInstance(config: AxiosRequestConfig): AxiosInstance {
const context = new Axios(config) //创建一个 Axios 实例 并传入 config 参数 并赋值给 context 变量
const instance = Axios.prototype.request.bind(context) //调用 request 方法 并传入 context 参数 并赋值给 instance 变量 绑定 this 指向 context

extend(instance, context) //调用 extend 方法 并传入 instance 和 context 参数 实现 instance 对象继承 context 对象
return instance as AxiosInstance //返回 instance 对象 并断言为 AxiosInstance 类型
}

const axios = createInstance(defaults) //调用 createInstance 方法 并传入 defaults 参数 并赋值给 axios 变量

export default axios

合并策略实现

创建 core / mergeConfig.ts

config1 为默认配置

config2 代表用户传入的自定义配置

实现示例 (假设 config1config2 如下:)

1
2
3
4
5
6
7
8
9
10
11
12
13
const config1 = {
timeout: 1000,
headers: {
'Content-Type': 'application/json',
},
}

const config2 = {
timeout: 2000,
headers: {
Authorization: 'Bearer token',
},
}

合并 timeout 属性时,val1 就是 config1.timeout 的值 1000val2 就是 config2.timeout 的值 2000

timeout 逻辑

mergeConfig.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
import { AxiosRequestConfig } from '../types'

const strats = Object.create(null) //合并策略 key为属性名 value为合并策略函数

function defaultStrat(val1: any, val2: any): any {
//默认合并策略
return typeof val2 !== 'undefined' ? val2 : val1 //优先取val2 如果val2不存在则取val1
}
function fromVal2Strat(val1: any, val2: any): any {
if (typeof val2 !== 'undefined') {
//忽略val1 只取val2
return val2
}
}

const stratKeysFromVal2 = ['url', 'params', 'data']

stratKeysFromVal2.forEach((key) => {
//对于url params data属性 只取val2
strats[key] = fromVal2Strat
})

export default function mergeConfig(
config1: AxiosRequestConfig,
config2?: AxiosRequestConfig
): AxiosRequestConfig {
if (!config2) {
config2 = {}
}
const config = Object.create(null) //合并后的配置结果

for (let key in config2) {
mergeField(key)
}

for (let key in config1) {
if (!config2[key]) {
mergeField(key)
}
}

function mergeField(key: string): void {
const strat = strats[key] || defaultStrat //合并策略
config[key] = strat(config1[key], config2![key])
}

return config
}

types / index.ts 添加自动索引

1
2
3
4
export interface AxiosRequestConfig {
...
[propName: string]: any //字符串自动索引
}

headers 逻辑

合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepMergeStrat(val1: any, val2: any): any {
if (isPlainObject(val2)) {
//如果val2是对象
return deepMerge(val1, val2) //递归合并
} else if (typeof val2 !== 'undefined') {
//如果val2不是对象 且val2存在
return val2
} else if (isPlainObject(val1)) {
//如果val2不是对象 且val2不存在 且val1是对象
return deepMerge(val1) //递归合并
} else if (typeof val1 !== 'undefined') {
//如果val2不是对象 且val2不存在 且val1不是对象 且val1存在
return val1
}
}

deepMerge 工具函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export function deepMerge(...objs: any[]): any {
// 深度合并对象
const result = Object.create(null)

objs.forEach((obj) => {
if (obj) {
Object.keys(obj).forEach((key) => {
const val = obj[key]
if (isPlainObject(val)) {
if (isPlainObject(result[key])) {
result[key] = deepMerge(result[key], val)
} else {
result[key] = deepMerge(val)
}
} else {
result[key] = val
}
})
}
})

return result
}

在请求中应用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
request(url: any, config: any): AxiosPromise { // 实现request方法传入参数
。。。
if (typeof url === 'string') {
if (!config) {
config = {}
}
config.url = url
} else {
config = url
}

//发送请求之前应用
config = this.mergeConfig(this.defaults,config) // 合并config

const chain:PromiseChain<any>[] = [
{// 创建一个数组chain
resolved: dispatchRequest,
rejected: undefined
}
]
。。。
}
压缩

合并后的 headers 是一个复杂对象,多了 common、post、get 等属性

1
2
3
4
5
6
7
8
headers:{
common:{
Accept:'application/json,text/plain,*/*'
},
post: {
'Content-Type':'application/x-www-form-urlencoded'
}
}

我们需要压缩成一级

1
2
3
4
headers:{
Accept:'application/json,text/plain,*/*',
'Content-Type':'application/x-www-form-urlencoded'
}

Tip:对于 comman 中定义的 header 字段,我们都要提取,而对于 post、get 这类提取,需要和该次请求的方法对应。

headers.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
export function flattenHeaders(headers: any, method: Method): any {
// flattenHeaders 函数接收 headers 和 method 参数 将 headers 中的属性名规范化 并删除不必要的属性 并返回一个新的 headers 对象
if (!headers) {
// 如果 headers 不存在
return headers // 返回 headers
}
headers = deepMerge(headers, null) // 调用 deepMerge 函数将 headers 深度合并
const methodsToDelete = [
// 定义一个数组 methodsToDelete 存储需要删除的 headers 属性名
'delete',
'get',
'head',
'options',
'post',
'put',
'patch',
'common',
]
methodsToDelete.forEach((method) => {
// 遍历 methodsToDelete 数组
delete headers[method] // 删除 headers 的属性名
})

return headers // 返回 headers
}

dispatchRequest.ts 应用函数

1
2
3
4
5
6
7
function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.headers = transformHeaders(config) // 先处理 headers
config.data = transformRequestData(config) // 再处理 data
config.headers = flattenHeaders(config.headers, config.method!) //处理 headers 扁平化
}

更多配置

transfromRequest & transfromResponse

官方 axios 中提供transfromRequest&transfromResponse,值为**一个数组或一个函数**

这两个属性允许用户在 数据发送到服务器之前(只适用于 put、post、patch,而且可以修改 headers 对象) / 把响应数据传递给 then 或 catch 之前 对其修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
axios({
transformRequest: [
(function(data) {
return qs.stringify(data); // 自定义的 transformRequest
}),
...axios.defaults.transformRequest // 保留默认的 transformRequest
],
transformResponse: [
...axios.defaults.transformResponse, // 展开默认的 transformResponse
function(data) {
if (typeof data === 'object') { // 自定义的 transformResponse
data.b = 2; // 修改响应数据
}
return data;
}
],
url: '/config/post', // 请求的 URL
method: 'post', // 请求方法
data: {
a: 1 // 请求的数据
}
});
定义接口

types / index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export interface AxiosRequestConfig {
//config接口
url: string
method?: Method
data?: any
params?: any
headers?: any
responseType?: XMLHttpRequestResponseType
timeout?: number
transformRequest?: AxiosTransformer | AxiosTransformer[] //请求数据转换函数
transformResponse?: AxiosTransformer | AxiosTransformer[] //响应数据转换函数

[propName: string]: any //字符串自动索引
}

export interface AxiosTransformer {
//转换接口
(data: any, headers?: any): any
}

defaults.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
import { processHeaders } from './helpers/header'
import { transformRequest, transformResponse } from './helpers/data'

const defaults: AxiosRequestConfig = {
url: '',
method: 'get',
timeout: 0,
headers: {
common: {
Accept: 'application/json, text/plain, */*', //通用的请求头 接受的响应类型为json/plain文本*/*表示任意类型 优先级最低
},
},

transformRequest: [
function (data: any, headers: any): any {
processHeaders(headers, data) //处理请求头
return transformRequest(data) //处理请求数据
},
],
transformResponse: [
function (data: any): any {
return transformResponse(data) //处理响应数据
},
],
}
重构 transform 逻辑

core / transform.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { AxiosTransformer } from '../types'

export default function transform( //transform函数接收data headers fns三个参数
data: any,
headers: any,
fns?: AxiosTransformer | AxiosTransformer[]
): any {
if (!fns) {
//如果没有传入fns 直接返回data
return data
}
if (!Array.isArray(fns)) {
//如果fns不是数组 将其转为数组
fns = [fns]
}
fns.forEach((fn) => {
//遍历fns数组 依次调用fn函数
data = fn(data, headers)
})
return data
}

core / dispatchRequest.ts

1
import transform from './transform'
1
2
3
4
5
6
7
function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.headers = transformHeaders(config) // 先处理 headers
config.data = transformRequestData(config) // 再处理 data
config.headers = flattenHeaders(config.headers, config.method!) //处理 headers 扁平化
}

更改为 👇

1
2
3
4
5
6
function processConfig(config: AxiosRequestConfig): void {
// 处理 config
config.url = transformURL(config)
config.data = transform(config.data, config.headers, config.transformRequest) //处理 data
config.headers = flattenHeaders(config.headers, config.method!) //处理 headers 扁平化
}

删掉不用的东西

1
2
3
4
5
6
7
8
9
10
11
12
import { transformRequest, transformResponse } from '../helpers/data'

function transformRequestData(config: AxiosRequestConfig): void {
// 处理 data 调用 transformRequest 方法
config.data = transformRequest(config.data)
}

function transformHeaders(config: AxiosRequestConfig): any {
// 处理 headers 调用 processHeaders 方法
const { headers = {}, data } = config
return processHeaders(headers, data)
}

更改逻辑

1
2
3
4
5
function transformResponseData(res: AxiosResponse): AxiosResponse {
// 处理 response data
res.data = transform(res.data, res.headers, res.config.transformResponse)
return res
}

create

目前为止,我们的 axios 都是一个单例,一旦我们修改了 axios 的默认配置,会影响所有的请求。我们希望提供了一个axios.create的静态接口允许我们创建一个新的 axios 实例,同时允许我们传入新的配置和默认配置合并,并做为新的默认配置。

扩展新的接口

types / index.ts

1
2
3
4
export interface AxiosStatic extends AxiosInstance {
//axios静态接口
create(config?: AxiosRequestConfig): AxiosInstance //创建axios实例
}

axios.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { AxiosRequestConfig, AxiosStatic } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
import defaults from './defaults'
import mergeConfig from './core/mergeConfig'

function createInstance(config: AxiosRequestConfig): AxiosStatic {
const context = new Axios(config) //创建一个 Axios 实例 并传入 config 参数 并赋值给 context 变量
const instance = Axios.prototype.request.bind(context) //调用 request 方法 并传入 context 参数 并赋值给 instance 变量 绑定 this 指向 context

extend(instance, context) //调用 extend 方法 并传入 instance 和 context 参数 实现 instance 对象继承 context 对象
return instance as AxiosStatic //返回 instance 对象 并断言为 AxiosStatic 类型
}

const axios = createInstance(defaults) //调用 createInstance 方法 并传入 defaults 参数 并赋值给 axios 变量

axios.create = function create(config) {
//axios.create 方法接收一个 config 参数 并返回一个 createInstance 方法的调用结果 并传入 mergeConfig 方法的调用结果 并传入 defaults 和 config 参数
return createInstance(mergeConfig(defaults, config))
}

export default axios