bunny笔记|TypeScript从零重构axios(未完待续

2022-08-06  本文已影响0人  一只小小小bunny

axios JS库 学习内容

优秀工具集成

课程安排:

理论(20%)+实战(80%)
需求分析-代码实现-demo演示
电子书辅助
https://git.imooc.com/coding-330/ts-axios-doc

课程收获

安装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
初始化项目

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

在gitHub中创建一个库
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

//axios传入对象发送请求的基本操作
axios({
    method:'get',
    url:'/simple/get',
    parems:{
        a:1,
        b:2
    }
})

//创建入口文件(在src目录下先建一个index.ts文件作为整个库的入口文件,然后定义一个axios方法,并把它导出)
function axios(config){
    
}

export default axios

//请求逻辑 模块化思想,新建一个文件夹实现后导出引入使用,编写好之后在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)

}

//多入口,多页的配置
entry:
//了解webpack.config.js和server.js
//simple下的index.html
//库的入口 app.ts index.ts


05

//参数值为数组
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中已存在的参数等

//在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)发送请求

//example/base下的demo

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

axios({
    method:'post',
    url:'/base/post,
    headers:{
        'content-type':'application/json;charset=utf-8'
        },
        data:{
            a:1,
            b:2
        }
})

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
}
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])
        }
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

//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.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 异常情况处理

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)

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)
})
//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)
}

(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.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.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)

(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)
    }
  ],

(未完待续)

上一篇下一篇

猜你喜欢

热点阅读