bunny笔记|TypeScript从零重构axios(未完待续
axios JS库 学习内容
- 项目脚手架
- 基础功能实现
- 异常清况处理
- 接口扩展
- 拦截器实现
- 配置化实现
- 取消功能实现
- 更多功能实现
优秀工具集成
- Jest (做单元测试)
- Commitizen(生成规范化的题标注释)
- PollupJS(打包构建项目)
- TSLint(保证代码风格一致性)
- Prettier(美化代码格式)
- Semantic release(管理版本和发布)
- husk (帮助我们更简单地使用git hooks)
- Conventional changelog(通过代码提交信息自动生成change log)
课程安排:
- 课程导学(1)
- TypeScript 常用语法讲解(2-3)
- 从零实现一个Ts-axios库(4-11)
- ts-axios库单元测试、构建和发布(12-14)
理论(20%)+实战(80%)
需求分析-代码实现-demo演示
电子书辅助
https://git.imooc.com/coding-330/ts-axios-doc
课程收获
- 学会使用TypeScript开发实际项目
- 学会造轮子,并学会写单元测试
- 学会使用先进的前端工具辅助开发
- 完全掌握axios的实现原理
- 内功修炼,个人技术能力提升
安装nodejs git npm vscode等基础工具
全局安装typescript
npm install -g typescript
//npm查看是否全局安装
npm ls typescript -g
yarn global add typescript
//ts-node的安装和使用
yarn global add ts-node
//也可以安装cnpm来全局安装typescript 再移除cnpm
//需要配置(path路径)电脑管理`完全控制`的权限
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm -v
cnpm install -g typescript
npm uninstall -g cnpm --registry=https://registry.npm.taobao.org
第3章 (基础 可跳过)
项目部分
04
初始化项目
- 需求分析
- Features功能
在浏览器端使用XML HttpRequest对象通讯
支持Promise API
支持请求和响应的拦截器
支持请求数据和响应数据的转换
支持请求的取消
Json数据的自动转换
客户端防止XSRF
- 创建代码仓库
在gitHub中创建一个库
- TypeScript library starter
(可以在官方地址学习和使用)
git clone https://github.com/alexjoverm/typescript-library-starter.git ts-axios
cd ts-axios
npm install
- 关联远程分支
//查看远程分支
git remote -v
//添加远程分支
git remote add origin (远程分支github的地址)
//查看远程分支
git remote -v
//拉取远程分支的代码
git pull origin master
//如果报错 可删除重复文件 rm (文件名,如README.md)
//查看分支
git branch
//将代码从工作区添加到暂存区
git add .
//提交代码(git commit -m)
//可以安装commit工具
npm run commit
(可以配置提交信息等)
//发送到远程分支
git push origin master
- 创建入口文件
(实现简单的发送请求功能,即客户端通过XMLHttpRequest对象把请求发送到server端,server端能收到请求并响应)
//axios传入对象发送请求的基本操作
axios({
method:'get',
url:'/simple/get',
parems:{
a:1,
b:2
}
})
//创建入口文件(在src目录下先建一个index.ts文件作为整个库的入口文件,然后定义一个axios方法,并把它导出)
function axios(config){
}
export default axios
- 利用xmlHttpRequest发送请求
(新建一个xhr.ts文件)
//请求逻辑 模块化思想,新建一个文件夹实现后导出引入使用,编写好之后在index.ts中import
import { AxiosRequestConfig } from "./types"
export default function(config:AxiosRequestConfig):void{
const {data=null,url,method='get'}=config
const request = new XMLHttpRequest()
//可以在MDN查阅XMLHttpRequest相关知识
request.open(method.toUpperCase(),url,true)
request.send(data)
}
- demo编写
(利用Nodejs的express库去运行demo,利用webpack来作为demo的构建工具。)
//多入口,多页的配置
entry:
//了解webpack.config.js和server.js
//simple下的index.html
//库的入口 app.ts index.ts
05
- 处理请求url参数-需求分析
(url解析到的传来的参数数据实际上是把params对象的key和value拼接到url上的,如:/base/get?a=1&b=2)
//参数值为数组
axios({
method:'get',
url:'/base/get',
params:{
foo:['bar','baz']
}
})
//得到的url是/base/get?foo[]=bar&foo[]=baz
//参数值为对象
axios({
method:'get',
url:'/base/get',
params:{
foo:{
bar:'baz'
}
}
})
//得到的url是/base/get?foo=%7B22bar%22:%22%7D.foo后面拼接的是{"bar":"baz"}encode后的结果
//参数值为Date类型 以及 特殊字符支持 忽略为空值 丢弃url中的哈希标志 保留url中已存在的参数等
- 处理请求url参数-buildURL函数实现
(src下新建一个helpers文件夹作为一个独立模块管理,用于存放辅助工具以及方法等)//处理url.ts编写 //处理uilt.ts编写
- 处理请求url参数-实现url参数处理逻辑
//在src下的index.ts处理
import {buildURL} from './helpers/url'
function processConfig(config:AxiosRequestConfig):void{
config.url =transformURL(config)
}
function transformURL(config:AxiosRequestConfig):string{
const {url,params} = config
return buildURL(url,params)
}
//在function axios()方法调用processConfig(config),即预处理配置后再调用xhr(config)发送请求
- 处理请求url参数-demo编写
//example/base下的demo
- 处理请求body数据-需求分析+实现
(通过执行XMLHttpRequest对象实例的send方法来发送请求,并通过该方法的参数设置请求body数据,我们发现send方法的参数支持Document和BodyInit类型,BodyInit包括了Blob,BufferSource,FormData,URLSearchParams,ReadableStream,USVSting,当没有数据的时候,我们还可以传入null)
但是我们最常用的场景还是传一个普通对象给服务器,如:
axios({
method:'post',
url:'/base/post',
data:{
a:1,
b=2
}
})
//这个时候data是不能直接传给send函数的,我们需要把它转换成JSON字符串
在src/helpers下新建一个data.ts文件
//(1)先到uilt下添加一个方法,用于判断是否是普通对象
export function isPlainObject(val:any):val is Object{
return toString.call(val) === '[object Object]'
}
//(2)在data.ts下引入使用
import { isPlainObject } from "./util";
export function transformRequest(data:any):any{
if(isPlainObject(data)){
return JSON.stringify(data)
}
return data
}
//(3)修改url.ts文件下的isObject改为isPlainObject
//isObject的方法暂且就用不到了 可先注释
(4)实现请求body的逻辑 在src/index.ts新增一个方法
import {transformRequest} from './helpers/data'
function transformRequestData(config:AxiosRequestConfig):any{
return transformRequest(config.data)
}
//再到processConfig中调用transformRequestData
config.data = transformRequestData(config)
(5)查看base文件的demo
- 处理请求header-需求分析
(当我们做了请求数据的处理,打data转换成JSON字符串,但是数据发送到服务器的时候,服务器并不能正常解析我们发送的数据,因为我们并没有给请求header设置正确的Content-Type。所以首先我们要支持发送请求的时候,可以支持配置headers属性。
并且在当我们传入的data为普通对象的时候,headers如果没有配置content-type属性,需要自动设置请求的header的content-type的字段为:application/json;charset=utf-8)
axios({
method:'post',
url:'/base/post,
headers:{
'content-type':'application/json;charset=utf-8'
},
data:{
a:1,
b:2
}
})
- 处理请求header-processHeaders函数实现
(在helpers目录下新建一个hesders.ts的文件)
import { isPlainObject } from "./util";
function normalizeHeaderName(headers: any, normalizeHeaderName: string): void {
if (!headers) {
return
}
Object.keys(headers).forEach((name) => {
if (name !== normalizeHeaderName && name.toLocaleUpperCase() === normalizeHeaderName.toLocaleUpperCase()) {
headers[normalizeHeaderName] = headers[name]
delete headers[name]
}
})
}
export function processHeaders(headers: any, data: any) {
normalizeHeaderName(headers,'Content-Type')
if (isPlainObject(data)) {
if (headers && !headers['Content-Type']) {
headers['Content-Type'] = 'application/json;charset=utf-8'
}
}
return headers
}
- 处理请求header-实现请求header处理逻辑
(在src/types/index.ts的接口AxiosRequestConfig方法下添加headers?:any接口的类型定义。在到src下index.ts新增transformHeaders方法)
import {processHeaders} from './helpers/headers'
function transformHeaders(config:AxiosRequestConfig){
//解析处理。(headers={}赋初始值)
const {headers={},data}=config;
return processHeaders(headers,data)
}
// 在processConfig方法中调用transformHeaders方法,注意:要在transformRequestData()方法之前先调用。config.headers=transformHeaders(config)
headers要到xhr.ts的request发送请求才有用,带xhr.ts下的request.open()方法之后添加
//添加headers解析
const {data=null,url,method='get',headers}=config
Object.keys(headers).forEach((name)=>{
if(data===null && name.toLowerCase() === 'content-type'){
delete headers[name]
}else{
request.setRequestHeader(name,headers[name])
}
-
处理请求header-demo编写
base的demo
运用浏览器调试端的nextwork的发送请求以及header等的相关数据的查看 -
获取响应数据-需求分析+parseHeader函数实现及应用
(我们发送的请求都可以从网络层接收到服务器返回的数据,但是代码层面并没有做任何关于返回数据的处理,我们希望能处理服务器响应的数据,并支持Promise链式调用的方式)
axios({
method:'post',
url:'/base/post',
data:{
a:1,
b:2
}
}).then((res)=>{
console.log(res)
})
(我们可以拿到res对象,并且我们希望该对象包括:服务器返回的数据data,HTTP状态码status,状态消息statusText,响应头headers、请求配置对象config以及请求的XMLHttpReauest对象实例request)
(1)类型接口的定义。 在types/index.ts下处理
export interface AxiosResponse{
data:any,
status:number,
statusText:string,
headers:any,
config:AxiosRequestConfig,
request:any
}
export interface AxiosPromise extends Promise<AxiosResponse>{
}
//另外再给AxiosRequestConfig接口扩展定义一个responseType接口。
responseType?:XMLHttpRequestResponseType
(2)获取响应数据的逻辑,改造xhr.ts文件
//请求逻辑 模块化思想,新建一个文件夹实现后导出引入使用
import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from "./types"
export default function xhr (config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve) => {
const { data = null, url, method = 'get', headers, responseType } = config
const request = new XMLHttpRequest()
//可以在MDN查阅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)
})
}
(3)改造src下index.ts的文件
function axios(config:AxiosRequestConfig):AxiosPromise{
processConfig(config)
return xhr(config)
}
(4)看base的demo
- 处理响应header-需求分析+transformResponse函数实现与应用
(我们通过XMLHttpResquest对象的getAllResponceHeaders方法获取到的值是一段字符串,每一行都是以回车符和换行符\r\n结束,它们是每个hueader属性的分隔符,我们希望把字符串解析成为一个对象结构)
//headers.ts添加转换方法
export function parseHeaders(headers: string): any {
let parased = Object.create(null)
if (!headers) {
return parased
}
headers.split('\r\n').forEach((line) => {
let [key, val] = line.split(':');
key = key.trim().toLowerCase()
if (!key) {
return
}
if (val) {
val = val.trim()
}
parased[key] = val
})
return parased
}
//xhr.ts 改造
import { parseHeaders } from "./helpers/headers"
//调用parseHeaders
const responseHeaders = parseHeaders(request.getAllResponseHeaders())
- 处理响应data-需求分析+transformResponse函数实现与应用
(在我们不去设置responseType的情况下,当服务器返回给我们的数据是字符串类型,我们可以尝试去把它转化成一个JSON对象)
//在data.ts下添加转换方法
export function transformResponse(data: any): any {
if (typeof data === 'string') {
try {
data = JSON.parse(data)
} catch (e) {
//do nothing
}
}
return data
}
//src/index.ts 改造
(2)
import {transformRequest,transformResponse} from './helpers/data'
(3)
return xhr(config).then((res)=>{
return transformResponseData(res)
})
(1)
function transformResponseData(res:AxiosResponse){
res.data = transformResponse(res.data)
return res
}
06 异常情况处理
- 错误处理-需求分析+网络错误+超时错误+非200状态码
(当网络出现异常(比如不通)的时候发送请求会触发XMLHttpRequest对象实例的error事件,于是我们可以在onerror的事件回调函数中捕获此类错误)
我们在xhr函数中添加如下代码
request.onerror = function handleError(){
reject(new Error('Network Error')
}
(1)在xhr.ts文件中改造 异常改造
return new Promise((resolve,reject) => {...}
request.onerror = function hadleError(){
reject(new Error('Network Error'))
}
处理超时错误(我们可以设置某个请求的超时事件timeout,也就是当请求发送后超过某个时间后仍然没有收到响应,则请求终止,并触发timeout事件。
请求默认的超时时间时0,即用不超时,所以我们首先需要允许程序可以配置超时时间)
export interface AxiosRequestConfig{
//....
timeout?:number
}
(2)超时错误处理
//type/index.ts下的AxiosRequestConfig接口定义下添加timeout可选接口
timeout?:number
//在xhr.ts解析的接口也添加timeout
const { data = null, url, method = 'get', headers, responseType,timeout} = config
//超时处理
if (timeout) {
request.timeout = timeout;
}
//超时处理
request.ontimeout =function handleTimeout(){
reject(new Error('Timeout of ${timeout} ms exceeded'))
}
(3) 处理非200状态码(对于一个正常的请求,往往会返回200-300之间的Http状态码,对于不在这个区间的状态码,我们也把它们认为是一种错误情况做处理)
//在xhr.ts下处理
request.onreadystatechange = function handleLoad() {
//非200处理
if(request.status ===0){
return
}
}
//定义一个辅助函数,处理reponse的其它情况下
//处理状态非200 (定义一个辅助函数,处理reponse的其它情况下)
function handleResponse(response:AxiosResponse):void{
if(response.status>=200 && response.status<300){
resolve(response)
}else{
reject(new Error('Request failed with status code ${response,status}'))
}
}
//并将resolve(response)调用改为handleResponse(response)
(4)查看error文件demo
(打开浏览器端的offline,查看error)
- 错误信息增强-需求分析
(上一节 我们已经捕获积累AJAX的错误,但是对于错误信息提供的非常有限,我们希望对外提供的信息不仅仅包含错误文本信息,还包括了请求对象配置config,错误代码code,XMLHttpRequest对象实例request以及自定义响应对象response。这样对于应用方来说,它们就可以捕获到这些错误的详细信息,做进一步的处理,以下对错误信息做增强)
axios({
method: 'get',
url: '/error/timeout',
timeout: 2000
}).then((res) => {
console.log(res)
}).catch((e: AxiosError) => {
console.log(e.message)
console.log(e.config)
console.log(e.code)
console.log(e.request)
console.log(e.isAxiosError)
})
- 错误信息增强-创建AxiosError类
(1)先来定义AxiosError类型接口,用于外部使用 types/index.ts
//isAxiosError:boolean
//定义接口
export interface AxiosError extends Error{
isAxiosError:boolean
config:AxiosRequestConfig
code?:string | null
request?:any
response?:AxiosResponse
}
(2)创建error.ts helpers/error.ts
import { AxiosRequestConfig, AxiosResponse } from "../types"
export class AxiosError extends Error {
isAxiosError: boolean
config: AxiosRequestConfig
code?: string | null
request?: any
response?: AxiosResponse
constructor(
message: string,
config: AxiosRequestConfig,
code?: any,
request?: any,
response?: AxiosResponse) {
super(message)
this.config = config
this.code = code
this.request = request
this.response = response
this.isAxiosError = true
Object.setPrototypeOf(this, AxiosError.prototype)
}
}
export function createError(
message: string,
config: AxiosRequestConfig,
code?: any,
request?: any,
response?: AxiosResponse
) {
const error = new AxiosError(message, config, code, request, response)
}
- 错误信息增强-createError方法应用+导出类型定义
(1)在xhr.ts文件中改造 替换方法new error
import { createError } from "./helpers/error"
// reject(new Error('Network Error'))
reject(createError('Network Error',config,null,request))
// reject(new Error('Timeout of ${timeout} ms exceeded'))
reject(createError('Timeout of ${timeout} ms exceeded',config,'ECONNABORTED',request))
// reject(new Error('Request failed with status code ${response,status}'))
reject(createError('Request failed with status code ${response,status}',config,null,request,response))
(2)src目录下新建一个axios.ts,并将原src下的index.ts的所有代码copy到axios.ts下并清空,在编写导入
//index.ts
import axios from './axios'
export * from './types'
export default axios
07 接口扩展
- axios接口扩展
(1)扩展接口-需求分析
为了用户更加方便使用axios发送请求,我们可以为所有支持请求方法扩展一些接口,如:
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.options(url[,config])
axios.post(url[,data[,config]])
axios.put(url[,data[,config]])
axios.patch(url[,data[,config]])
如果使用了这些方法,我们就不必在config中指定url、method、data这些属性了。从需求上来看,axios不再单单是一个方法,更像是一个混合对象,本身是一个方法,又有很多方法属性。如何实现混合对象呢?
(2)接口类型定义
(根据需求分析,混合对象axios本身是一个函数,我们可基于类的方式去实现它的的方法属性,然后把这个类的原型属性和自身属性再拷贝到axios上。看实例:)
//types/index.ts
//接口扩展
export interface 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{
(config:AxiosRequestConfig):AxiosPromise
}
(3)在src下新建一个core文件夹,生成一个Axios.ts(一个类)
将xhr.ts复制移动至core文件下,另外再新建一个dispatchRequest.ts文件,将src下的axios.ts文件下的代码复制过来。注意:要修改引入路径以及导入导出书写部分
export default function dispatchRequest(config:AxiosRequestConfig):AxiosPromise{ //.... }
Axios.ts
import { AxiosPromise, AxiosRequestConfig, Method } from "../types";
import dispatchRequest from "./diapatchRequest";
export default class Axios{
request(config:AxiosRequestConfig):AxiosPromise{
return dispatchRequest(config)
}
get(url:string,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithoutData('get',url,config)
}
delete(url:string,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithoutData('delete',url,config)
}
head(url:string,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithoutData('head',url,config)
}
options(url:string,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithoutData('options',url,config)
}
//get()与delete()等方法类似,可以合并写
_requestMethodWithoutData(method:Method,url:string,config?:AxiosRequestConfig):AxiosPromise{
return this.request(Object.assign(config || {},{
method,
url
}))
}
post(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithData('post',url,data,config)
}
put(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithData('put',url,data,config)
}
patch(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise{
return this._requestMethodWithData('patch',url,data,config)
}
//post()等方法类似,可以合并写
_requestMethodWithData(method:Method,url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise{
return this.request(Object.assign(config || {},{
method,
url,
data
}))
}
}
(4) 实现混合对象
在helpers下的util.ts下添加extend()方法
//扩展接口
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
}
return to as T & U
}
因为axios.ts的代码已经在core下做了模块化处理,所以可以都清除,再定义一个新的function函数
//src/axios.ts
import { AxiosInstance } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
function createInstance(): AxiosInstance {
const context = new Axios()
const instance = Axios.prototype.request.bind(context)
extend(instance, context)
return instance as AxiosInstance
}
const axios = createInstance()
export default axios
(5)axios函数重载
// types/index.ts
export interface AxiosInstance extends Axios{
(config:AxiosRequestConfig):AxiosPromise
(url:string,config?:AxiosRequestConfig):AxiosPromise
}
// core/Axios.ts
// request(config:AxiosRequestConfig):AxiosPromise{
// return dispatchRequest(config)
// }
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {}
}
config.url = url
} else {
config = url
}
return dispatchRequest(config)
}
(6)响应数据支持泛型
给types下的index.ts的 interface AxiosResponse()添加泛型接口
export interface AxiosResponse<T=any>{
data:T,
status:number,
statusText:string,
headers:any,
config:AxiosRequestConfig,
request:any
}
export interface AxiosPromise<T=any> extends Promise<AxiosResponse<T>>{
}
//接口扩展
export interface Axios{
request<T=any>(config:AxiosRequestConfig):AxiosPromise<T>
get<T=any>(url:string,config?:AxiosRequestConfig):AxiosPromise<T>
delete<T=any>(url:string,config?:AxiosRequestConfig):AxiosPromise<T>
head<T=any>(url:string,config?:AxiosRequestConfig):AxiosPromise<T>
options<T=any>(url:string,config?:AxiosRequestConfig):AxiosPromise<T>
post<T=any>(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise<T>
put<T=any>(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise<T>
patch<T=any>(url:string,data?:any,config?:AxiosRequestConfig):AxiosPromise<T>
}
//用于继承
export interface AxiosInstance extends Axios{
<T=any>(config:AxiosRequestConfig):AxiosPromise<T>
<T=any>(url:string,config?:AxiosRequestConfig):AxiosPromise<T>
}
08 拦截器实现(30min)
- axios 拦截器实现(运用Promise链式调用)
我们希望对于请求的发送和响应做拦截,也就是在发送请求之前和接收到响应之后做一些额外逻辑
//添加一个请求拦截器
axios.insterceptprs.request.use(function(config){
//发送请求之前可以做一些事情
return config;
},function(error){
//处理错误
return Promise.reject(error);
});
//添加一个响应拦截器
axios.insterceptors.response.use(function(response){
//处理响应数据
return response;
},function(error){
//处理响应错误
return Promise.reject(error);
});
在axios对象上有一个insterceptprs对象属性,该属性又有request和response两个属性,它们有一个use方法,use方法支持两个参数。第一个参数类似Promise的resolve函数,第二个参数类似Promise的reject函数,我们可以在resolve函数和reject函数中执行同步代码或者异步代码逻辑。
并且我们是可以添加多个拦截器的,拦截器的执行顺序是链式依次执行的方式。request拦截器:后添加的拦截器会在请求的过程先执行,response拦截器:先添加的拦截器会在响应后先执行
axios.interceptors.request.use(config=>{
config.header.test +='1'
return config
});
axios.interceptors.request.use(config=>{
config.headers.test +='2'
return config
})
此外我们也可以支持删除某个拦截器,如:
const myInterceptor = axios.interceptors.request.use(function{// ... })
axios.interceptors.request.eject(myInterceptor)
(1)拦截器管理类的实现
定义接口 types/index.ts
//拦截器接口
export interface AxiosInterceptorManage<T>{
use(resolve:ResolvedFn<T>,reject:RejectenFn):number
eject(id:number):void
}
export interface ResolvedFn<T>{
(val:T):T|Promise<T>
}
export interface RejectenFn{
(error:any):any
}
实现逻辑 core/新建一个interceptorManage.ts
import { ResolvedFn, RejectedFn } from '../types'
interface Interceptor<T> {
resolved: ResolvedFn<T>
rejected?: RejectedFn
}
export default class InterceptorManager<T> {
private interceptors: Array<Interceptor<T> | null>
constructor() {
this.interceptors = []
}
use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected
})
return this.interceptors.length - 1
}
//遍历拦截器
forEach(fn: (interceptor: Interceptor<T>) => void): void {
this.interceptors.forEach(interceptor => {
if (interceptor !== null) {
fn(interceptor)
}
})
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null
}
}
}
链式调用实现
修改request方法的逻辑,添加拦截器链式调用的逻辑
core/Axios.ts
import InterceptorManage from './interceptorManage'
interface Interceptors{
request:InterceptorManage<AxiosRequestConfig>
response:InterceptorManage<AxiosResponse>
}
interface PromiseChain<T>{
resolved:ResolvedFn<T> | ((config:AxiosRequestConfig)=>AxiosPromise)
rejected?:RejectedFn
}
export default class Axios {
interceptors:Interceptors
constructor(){
this.interceptors={
request:new InterceptorManage<AxiosRequestConfig>(),
response:new InterceptorManage<AxiosResponse>()
}
}
// request(config:AxiosRequestConfig):AxiosPromise{
// return dispatchRequest(config)
// }
request(url: any, config?: any): AxiosPromise {
if (typeof url === 'string') {
if (!config) {
config = {}
}
config.url = url
} else {
config = url
}
const chain:PromiseChain<any>[] =[{
resolved:dispatchRequest,
rejected:undefined
}]
this.interceptors.request.forEach(interceptor => {
chain.unshift(interceptor)
})
this.interceptors.response.forEach(interceptor => {
chain.push(interceptor)
})
let promise = Promise.resolve(config)
while(chain.length){
const {resolved,rejected} = chain.shift()!
promise=promise.then(resolved,rejected)
}
return promise
// return dispatchRequest(config)
}
(2)demo演示
-- 修改types下idex.ts下接口axios的接口方法中 interceptors:Interceptors
//添加
interceptors:{
request:AxiosInterceptorManage<AxiosRequestConfig>
response:AxiosInterceptorManage<AxiosResponse>
}
-- 再修改type/index.ts下的rejected为可选接口参数
-- 其它(略)
09 配置化实现(50 min)
- ts-axios配置化实现(运用策略模式)
在发送请求的时候可以传入一个配置参数,来决定请求的不同行为,就是说我们也希望ts+axios可以有默认配置,定义一些默认的行为,这样在发送每个请求,用户传递的配置可以和默认配置做一层合并。和官网axios库保持一致,可以给axios对象添加一个defaults属性,甚至可以直接修改这些默认配置
(1)实现合并配置
src下新建一个defaults.ts文件,默认配置
import { AxiosRequestConfig } from "./types";
const defaults: AxiosRequestConfig = {
method: 'get',
timeout: 0,
headers: {
common: {
Accept: 'application/json,text/plain,*/*'
}
}
}
const methodsNoData = ['delete', 'get', 'head', 'options']
methodsNoData.forEach(method => {
defaults.headers[method] = {}
})
const methodWithData = ['post', 'put', 'patch']
methodWithData.forEach(method => {
defaults.headers[method] = {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
export default defaults
接下来呢,到core下的Axios.ts中来运用defaults默认配置
export default class Axios {
interceptors:Interceptors
// interceptors:{
// request:AxiosInterceptorManage<AxiosRequestConfig>
// response:AxiosInterceptorManage<AxiosResponse>
// }
//配置默认配置
defaults:AxiosRequestConfig
constructor(initConfig:AxiosRequestConfig){
this.defaults=initConfig
this.interceptors={
request:new InterceptorManage<AxiosRequestConfig>(),
response:new InterceptorManage<AxiosResponse>()
}
}
}
在types下的index.ts也要添加defaults
//接口扩展
export interface Axios {
defaults:AxiosRequestConfig
interceptors:{
request:AxiosInterceptorManage<AxiosRequestConfig>
response:AxiosInterceptorManage<AxiosResponse>
}
request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
delete<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
head<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
options<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
}
在src下的axios.ts中添加一个参数
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)
const instance = Axios.prototype.request.bind(context)
extend(instance, context)
return instance as AxiosInstance
}
const axios = createInstance(defaults)
export default axios
不同的合并有不同的合并策略
在src/core/新建一个mergeConfig.ts
import { AxiosRequestConfig } from '../types'
import { deepMerge, isPlainObject } from '../helpers/util'
const strats = Object.create(null)
function defaultStrat(val1: any, val2: any): any {
return typeof val2 !== 'undefined' ? val2 : val1
}
function fromVal2Strat(val1: any, val2: any): any {
if (typeof val2 !== 'undefined') {
return val2
}
}
function deepMergeStrat(val1: any, val2: any): any {
if (isPlainObject(val2)) {
return deepMerge(val1, val2)
} else if (typeof val2 !== 'undefined') {
return val2
} else if (isPlainObject(val1)) {
return deepMerge(val1)
} else {
return val1
}
}
const stratKeysFromVal2 = ['url', 'params', 'data']
stratKeysFromVal2.forEach(key => {
strats[key] = fromVal2Strat
})
const stratKeysDeepMerge = ['headers', 'auth']
stratKeysDeepMerge.forEach(key => {
strats[key] = deepMergeStrat
})
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
}
在type下index.ts的interface AxiosRequestConfig方法下增添[propName:string]:any声明
在helpers下util.ts添加配置接口
//合并配置
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
}
helpers/header.ts
import { isPlainObject, deepMerge } from './util'
import { Method } from '../types'
export function flattenHeaders(headers: any, method: Method): any {
if (!headers) {
return headers
}
headers = deepMerge(headers.common, headers[method], headers)
const methodsToDelete = ['delete', 'get', 'head', 'options', 'post', 'put', 'patch', 'common']
methodsToDelete.forEach(method => {
delete headers[method]
})
return headers
}
在dispatchRequest.ts下processConfig方法下应用
import { flattenHeaders, processHeaders } from '../helpers/headers'
function processConfig(config: AxiosRequestConfig): void {
config.url = transformURL(config)
config.data = transform(config.data, config.headers, config.transformRequest)
config.headers = flattenHeaders(config.headers, config.method!)
}
(2)请求和响应配置化
transformRequest.ts
import { AxiosTransformer } from '../types'
export default function transform(
data: any,
headers: any,
fns?: AxiosTransformer | AxiosTransformer[]
): any {
if (!fns) {
return data
}
if (!Array.isArray(fns)) {
fns = [fns]
}
fns.forEach(fn => {
data = fn(data, headers)
})
return data
}
defauls.ts
transformRequest: [
function(data: any, headers: any): any {
processHeaders(headers, data)
return transformRequest(data)
}
],
transformResponse: [
function(data: any): any {
return transformResponse(data)
}
],
(未完待续)