基于ts重构axios

ustbhuangyi 老师的 基于TypeScript从零重构axios学习记录。

知识点

TypeScript 常用语法:

基础类型函数变量声明接口泛型类型推新高级类型

axios js库:

项目脚手架基础功能实现异常情况处理接口扩展拦截器实现配置化实现取消功能实现其他功能实现等等

主要工具:
JestTSLintCommitizenPrettierRollupJSSemantic release

基本语法#

点我

需求分析#

Features#

  • 在浏览器使用 XMLHttpRequest 对象通讯
  • 支持 Promise API
  • 支持请求和响应的拦截器
  • 支持请求数据和响应数据的转换
  • 支持请求的取消
  • JSON数据的自动转换
  • 客户端防止 XSRF

基于 XMLHttpRequest 编写基本请求代码#

处理请求数据:url/body/headers#

src/types/index.ts

Copy
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 headers?: any }

src/xhr.ts

Copy
import { AxiosRequestConfig } from './types' export default function xhr(config: AxiosRequestConfig): void { const { data = null, url, method = 'get', headers } = config const request = new XMLHttpRequest() // method,url,async request.open(method.toUpperCase(), url, true) Object.keys(headers).forEach(name => { if (data === null && name.toLowerCase() === 'content-type') { delete headers[name] } else { request.setRequestHeader(name, headers[name]) } }) request.send(data) }

src/index.ts

Copy
import { AxiosRequestConfig } from './types' import { buildURL } from './helpers/url'; import { transformRequest } from './helpers/data'; import xhr from './xhr' import { processHeaders } from './helpers/header'; function axios(config: AxiosRequestConfig): void { processConfig(config) xhr(config) } function processConfig(config: AxiosRequestConfig): void { config.url = transformURL(config) config.data = transformRequestData(config) config.headers = transformHeaders(config) } function transformHeaders(config: AxiosRequestConfig): void { const { headers = {}, data } = config return processHeaders(headers, data) } function transformRequestData(config: AxiosRequestConfig): void { return transformRequest(config.data) } function transformURL(config: AxiosRequestConfig): string { const { url, params } = config; return buildURL(url, params) } export default axios

工具类#

data.ts

Copy
import { isPlainObject } from "./util"; export function transformRequest(data: any): any { if (isPlainObject(data)) { return JSON.stringify(data) } return data }

headers.ts

Copy
import { isPlainObject } from "./util" function normalizeHeaderName(headers: any, normalizedName: string): void { if (!headers) { return } Object.keys(headers).forEach(name => { if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { headers[normalizedName] = headers[name] delete headers[name] } }) } export function processHeaders(headers: any, data: any): any { normalizeHeaderName(headers, 'Content-Type') if (isPlainObject(data)) { if (headers && !headers['Content-Type']) { headers['Content-Type'] = 'application/json;charset=utf-8' } } return headers }

url.ts

Copy
import { isDate, isPlainObject } from './util' function encode(val: string): string { return encodeURIComponent(val) .replace(/%40/g, '@') .replace(/%3A/ig, ':') .replace(/%24/g, '**util.ts** ​```tsx const toString = Object.prototype.toString export function isDate(val: any): val is Date { return toString.call(val) === '[object Date]' } export function isPlainObject(val: any): val is Object { return toString.call(val) === '[object Object]' }

处理响应数据#

定义响应接口

types/index

Copy
export interface AxiosResponse { data: any status: number statusText: string headers: any config: AxiosRequestConfig request: any } export interface AxiosPromise extends Promise<AxiosResponse> { }

处理 headers 的数据

helpers/header.ts

Copy
export function processHeaders(headers: any, data: any): any { normalizeHeaderName(headers, 'Content-Type') if (isPlainObject(data)) { if (headers && !headers['Content-Type']) { headers['Content-Type'] = 'application/json;charset=utf-8' } } return headers } export function parseHeaders(headers: string): any { let parsed = Object.create(null) if (!headers) { return headers } headers.split('\r\n').forEach(line => { let [key, val] = line.split(':') key = key.trim().toLowerCase() if (!key) { return } if (val) { val = val.trim(); } parsed[key] = val }) return parsed }

处理 响应data

helpers/data.ts

Copy
export function transformResponse(data: any): any { if (typeof data === 'string') { try { data = JSON.parse(data) } catch (e) { // do nothing } } return data }

修改 xhr, 返回一个 Promise

xhr.ts

Copy
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types' import { parseHeaders } from './helpers/headers' export default function xhr(config: AxiosRequestConfig): AxiosPromise { return new Promise(resolve => { const { data = null, url, method = 'get', headers, responseType } = config const request = new XMLHttpRequest() if (responseType) { request.responseType = responseType } // method,url,async request.open(method.toUpperCase(), url, true) request.onreadystatechange = function handleLoad() { if (request.readyState !== 4) { return } const responseHeaders = parseHeaders(request.getAllResponseHeaders()) const responseData = responseType && responseType !== 'text' ? request.response : request.responseText const response: AxiosResponse = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config, request } resolve(response) } Object.keys(headers).forEach(name => { if (data === null && name.toLowerCase() === 'content-type') { delete headers[name] } else { request.setRequestHeader(name, headers[name]) } }) request.send(data) }) }

具体代码地址

)

Copy
.replace(/%2C/ig, ',') .replace(/%20/g, '+') .replace(/%5B/ig, '[') .replace(/%5D/ig, ']')

}

export function buildURL(url: string, params?: any): string {
if (!params) {

Copy
return url

}
const parts: string[] = []
Object.keys(params).forEach(key => {

Copy
const val = params[key] if (val === null || typeof val === 'undefined') { return } let values = [] if (Array.isArray(val)) { values = val key += '[]' } else { values = [val] } values.forEach(val => { if (isDate(val)) { val = val.toISOString() } else if (isPlainObject(val)) { val = JSON.stringify(val) } parts.push( `${encode(key)}=${encode(val)}` ) })

})
let serializedParams = parts.join('&')
if (serializedParams) {

Copy
const markIndex = url.indexOf('#') if (markIndex !== -1) { url = url.slice(0, markIndex) } url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams

}
return url
}

Copy
**util.ts** ​```tsx const toString = Object.prototype.toString export function isDate(val: any): val is Date { return toString.call(val) === '[object Date]' } export function isPlainObject(val: any): val is Object { return toString.call(val) === '[object Object]' }

处理响应数据#

定义响应接口

types/index

Copy
export interface AxiosResponse { data: any status: number statusText: string headers: any config: AxiosRequestConfig request: any } export interface AxiosPromise extends Promise<AxiosResponse> { }

处理 headers 的数据

helpers/header.ts

Copy
export function processHeaders(headers: any, data: any): any { normalizeHeaderName(headers, 'Content-Type') if (isPlainObject(data)) { if (headers && !headers['Content-Type']) { headers['Content-Type'] = 'application/json;charset=utf-8' } } return headers } export function parseHeaders(headers: string): any { let parsed = Object.create(null) if (!headers) { return headers } headers.split('\r\n').forEach(line => { let [key, val] = line.split(':') key = key.trim().toLowerCase() if (!key) { return } if (val) { val = val.trim(); } parsed[key] = val }) return parsed }

处理 响应data

helpers/data.ts

Copy
export function transformResponse(data: any): any { if (typeof data === 'string') { try { data = JSON.parse(data) } catch (e) { // do nothing } } return data }

修改 xhr, 返回一个 Promise

xhr.ts

Copy
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types' import { parseHeaders } from './helpers/headers' export default function xhr(config: AxiosRequestConfig): AxiosPromise { return new Promise(resolve => { const { data = null, url, method = 'get', headers, responseType } = config const request = new XMLHttpRequest() if (responseType) { request.responseType = responseType } // method,url,async request.open(method.toUpperCase(), url, true) request.onreadystatechange = function handleLoad() { if (request.readyState !== 4) { return } const responseHeaders = parseHeaders(request.getAllResponseHeaders()) const responseData = responseType && responseType !== 'text' ? request.response : request.responseText const response: AxiosResponse = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config, request } resolve(response) } Object.keys(headers).forEach(name => { if (data === null && name.toLowerCase() === 'content-type') { delete headers[name] } else { request.setRequestHeader(name, headers[name]) } }) request.send(data) }) }

具体代码地址

posted @   Scok  阅读(130)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
CONTENTS