一、在GitHub上创建一个代码仓库

找到仓库地址:git@github.com:QianDingweiCharles/ts-axios.git

二、项目配置

本地新建一个文件夹axios

用VScode打开,通过Typescript脚手架Typescript library starter搭建项目

命令行: git clone https://github.com/alexjoverm/typescript-library-starter.git axios

cd axios

查看远程分支:git remote -v,因为没有关联所以没有任何输出

关联远程分支:git remote add origin git@github.com:QianDingweiCharles/ts-axios.git

拉取远程分支并合并到当前的代码:git pull origin master

git branch 就可以看到本地也有master分支了

git push -u origin master

三、编写请求代码

//src/index.ts
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
function axios(config: AxiosRequestConfig): void {
xhr(config)
}
export default axios
//src/types/index.ts这是声明文件
export type Method = 'get' | 'GET'
| 'delete' | 'Delete'
| 'head' | 'HEAD'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
}

 四、处理请求参数

4.1、参数是数组

params:{foo: ['bar,'baz'']},最终请求的url是/base/get?foo[]=bar&foo[]=baz

4.2、参数是一个对象

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

4.3 、参数值是一个Date类型

params:{date}最终请求的url是/base/get?data=2019-04-01......,date后面拼接的是date.toISOString()的结果

4.4 特殊字符支持

对于字符@、:、¥、,空格,[,],我们是允许出现在url中的,不希望被encode

params:{foo:'@:$'}最终请求的url是/base/get?foo=@:$+,注意,我们会吧空格转成+

4.5空值忽略

params:{foo:bar,baz:null}最终的请求url是/base/get?foo=bar

4.6丢弃url中的哈希标记

axios({method:‘get’,url: '/base/get#hash',params:{foo:'bar'}})最终请求的url是/base/get?foo=bar

4.7保留url中已经存在的参数

axios({method:‘get’,url:'/base/get?foo=bar',params:{bar:'baz'}})最终的请求url是/base/get?foo=bar&bar=baz

//src/helpers/url.ts
import { isDate, isPlainObject } 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): string {
if (!params) {
return url
} const parts: string[] = [] Object.keys(params).forEach(key => {
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) {
const markIndex = url.indexOf('#')
//去掉哈希值
if (markIndex !== -1) {
url = url.slice(0, markIndex)
} url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
} return url
}

  

//src/helpers/util.ts
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]'
}

五、处理请求的body数据

需要将请求的data进行处理,如果是普通的对象,需要转换成JSON格式的数据

//src/helpers/data.ts
import { isPlainObject } from './util' export function transformRequest(data: any): any {
if (isPlainObject(data)) {
return JSON.stringify(data)
}
return data
} export function transformResponse(data: any): any {
if (typeof data === 'string') {
try {
data = JSON.parse(data)
} catch (e) {
// do nothing
}
}
return data
}

  

//src/index.ts
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
import { buildURL } from './helpers/url'
import { transformRequest } from './helpers/data'
function axios(config: AxiosRequestConfig): void {
processConfig(config)
xhr(config)
} function processConfig(config: AxiosRequestConfig) {
config.url = transformURL(config)
config.data = transformRequestData(config)
} function transformURL(config: AxiosRequestConfig) {
const { url, params } = config
return buildURL(url, params)
} function transformRequestData(config: AxiosRequestConfig) {
return transformRequest(config.data)
}
export default axios

六 、处理请求头

上一步对data进行了处理,但是content-type 为plain-text而不是application/json浏览器无法处理

//src/helpers/headers.ts
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
}

在配置接口中添加headers字段,用户可以设置headers

在src/index.ts加入处理请求头逻辑:

这样就可以了吗?其实并没有,真正发送request headers 的xhr我们并没有修改,下一步,修改xhr:

七、获取响应数据

在此之前我们发送的请求都是从网络层面接手服务端返回的数据,单数代码层面并没有做任何关于返回数据的处理,我们希望能处理服务端响应的数据,并支持Promise链式调用的方式,如下:

axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
}).then((res:any)=> {console.log(res)})

  我们可以拿到res对象,并且我们希望该对象包括:服务端返回的数据data,HTTP状态码status,状态消息,响应头headers,请求配置对象config,以及请求的XMLHttpRequest对象实例request。

7.1、定义接口类型

在src/types/index.ts

responseType让用户定义返回的类型,AxiosResponse是回调函数resolve传出去,也就是then方法里面得到的参数。AxiosPromise是返回的promise对象。

补充知识:

onreadystatechange:XMLHttpRequest.onreadystatechange 会在 XMLHttpRequest 的readyState 属性发生改变时触发 readystatechange 事件的时候被调用。

ready​State:属性返回一个 XMLHttpRequest  代理当前所处的状态。一个 XHR 代理总是处于下列状态中的一个。

getAllResponseHeaders() :方法返回所有的响应头,以\r\n分割的字符串,或者 null 如果没有收到任何响应.

修改src/xhr.ts:

import { AxiosRequestConfig, AxiosPromise,AxiosResponse } from './types'
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve) => {
const { method = 'get', url, data = null, headers,responseType } = config
const request = new XMLHttpRequest()
if(responseType){
request.responseType = responseType
}
request.open(method.toUpperCase(), url, true)
request.onreadystatechange = function handleLoad(){
if(request.readyState !==4){
return
}
const responseHeaders = request.getAllResponseHeaders()
const responseData = 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)
}) }

  修改src/index.ts

八、处理响应header+处理响应data

8.1 处理响应header

通过XMLHTTPRequest的getAllResponseHeaders方法获取的值是一串以\r\n的字符串,我们希望最终解析成一个对象结构。

在src/helpers/headers.ts中添加函数:

export function parseHeaders(headers: string): any {
let parsed = Object.create(null)
if (!headers) {
return
}
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
})
}

 修改xhr.ts修改responseHeaders:

8.2处理响应data

在我们不设置responseType的情况下,当服务端返回给我们的数据是字符串类型,我们可以尝试再把他转换成一个JSON 对象,例如:

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

 再src/helpers/data.ts中增加函数:

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

修改src/index.ts如下

  

九、异常情况处理

9.1 网络异常错误

当网络出现异常,比如不通的时候发送请求会触发XMLHttpRequest对象实例的error事件,于是我们可以在onerror的事件回调函数找那个捕获此类错误

修改src/xhr.ts如下:

9.2处理超时错误

当用户配置了超时时间时,如果超过了这个时间,那么将触发onTimeout事件。

在src/types/index.ts中的AxiosRequestConfig接口,添加timeout

修改xhr.ts

9.3 处理非200状态码

9.4错误信息增强

希望提供的错误信息不仅仅包含错误文本信息,还包括请求对象配置config、错误代码code,XMLHttpRequest对象实例request,以及自定义响应对象response。

在src/type/index.ts中增加接口:

export interface AxiosError extends Error {
config: AxiosRequestConfig
code?: string
request?: any
response?: AxiosResponse
isAxiosError?: boolean
}

  新增./src/helpers/error.ts

import { AxiosRequestConfig, AxiosResponse } from '../types'
export class AxiosErros extends Error {
config: AxiosRequestConfig
code?: string |null
request?: any
response?: AxiosResponse
isAxiosError: boolean
constructor(
message: string,
config: AxiosRequestConfig,
code?: string |null,
request?: any,
response?: AxiosResponse
) {
super(message)
this.config = config
this.code = code
this.request = request
this.response = response
this.isAxiosError = true
Object.setPrototypeOf(this,AxiosErros.prototype)
}
}
//工程函数
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string |null,
request?: any,
response?: AxiosResponse): AxiosErros{
return new AxiosErros(message,config,code,request,response)
}

  

然后新建src/axios.ts从index.ts中复制所有

//src/index.ts
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types'
import xhr from './xhr'
import { buildURL } from './helpers/url'
import { transformRequest, transformResponse } from './helpers/data'
import { processHeaders } from './helpers/headers'
function axios(config: AxiosRequestConfig): AxiosPromise {
processConfig(config)
return xhr(config).then((res) => {
return transformResponseData(res)
})
} function processConfig(config: AxiosRequestConfig) {
config.url = transformURL(config)
config.headers = transformHeaders(config)
config.data = transformRequestData(config)
} function transformURL(config: AxiosRequestConfig) {
const { url, params } = config
return buildURL(url, params)
} function transformRequestData(config: AxiosRequestConfig) {
return transformRequest(config.data)
} function transformHeaders(config: AxiosRequestConfig): void {
const { headers, data } = config
return processHeaders(headers, data)
} function transformResponseData(res: AxiosResponse) {
res.data = transformResponse(res)
return res
}
export default axios

  将src/index.ts改为:

import axios from './axios'
export * from './types' export default axios

其他请下载:https://files.cnblogs.com/files/QianDingwei/ts-axios-doc-master.zip

基于TypeScript从零重构axios的更多相关文章

  1. 使用Typescript重构axios(三十二)——写在最后面(总结)

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  2. 使用Typescript重构axios(一)——写在最前面

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  3. 使用Typescript重构axios(六)——实现基础功能:获取响应数据

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  4. 使用Typescript重构axios(十三)——让响应数据支持泛型

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  5. 使用Typescript重构axios(十四)——实现拦截器

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  6. 使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  7. 使用Typescript重构axios(二)——项目起手,跑通流程

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  8. 使用Typescript重构axios(三)——实现基础功能:处理get请求url参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  9. 使用Typescript重构axios(四)——实现基础功能:处理post请求参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

随机推荐

  1. 11g dataguard 类型、保护模式、服务

    一. Dataguard中的备库分为物理备库和逻辑备库及快照备库 备库是主库的一致性拷贝,使用一个主库的备份可以创建多到30个备库,将其加入到dataguard环境中,创建成功后,dataguard通 ...

  2. OpenCode:template

    ylbtech-OpenCode: 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   作者:ylbtech出处:http://ylbtec ...

  3. nodejs的mysql模块学习(二)连接数据库

    nodejs连接mysql的方式有两种 官方建议的第一种是 let mysql = require('mysql'); let connection = mysql.createConnection( ...

  4. eclipse修改workspace

    Eclipse是一款很强的Java IDE,我们在开始的时候,往往设定了默认的workspace,当用久在之后,我们可能要去更改一下workspace的位置,但是在启动的时候已经不会显示更改了.下面有 ...

  5. 4.xpath注入详解

    0x01 简介 XPath注入攻击是指利用XPath 解析器的松散输入和容错特性,能够在 URL.表单或其它信息上附带恶意的XPath 查询代码,以获得权限信息的访问权并更改这些信息.XPath注入发 ...

  6. 无监督学习:Linear Dimension Reduction(线性降维)

    一 Unsupervised Learning 把Unsupervised Learning分为两大类: 化繁为简:有很多种input,进行抽象化处理,只有input没有output 无中生有:随机给 ...

  7. linux命令之ll按时间和大小排序显示

    ll不是命令,是ls -l的别名 按大小排序 [root@localhost ~]# ll -Sh 按时间排序 [root@localhost ~]# ll -rt ll -t 是降序, ll -t ...

  8. Python中生成随机数

    目录 1. random模块 1.1 设置随机种子 1.2 random模块中的方法 1.3 使用:生成整形随机数 1.3 使用:生成序列随机数 1.4 使用:生成随机实值分布 2. numpy.ra ...

  9. git (Linux安装及使用教程)

    查看当前服务器是否有安装git git --version 如果有,那么查看版本号,是否是你想要的或最新的版本 如果不是自己想要的版本,那么执行以下命令可卸载当前版本 yum remove git 卸 ...

  10. ue4 weapon

    UE4版本4.17,不同版本api可能有差异 静态 1 在骨骼上加socket 在socket上右键-添加浏览资源-找到要添加的那个道具(这个只用来看效果,调位置,不会显示到最终效果中),调整sock ...