欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

axios的基本用法、AxiosRequestConfig、axios实例的拦截器、axos封装

最编程 2024-05-31 22:25:05
...

本文整理来自深入Vue3+TypeScript技术栈-coderwhy大神新课,只作为个人笔记记录使用,请大家多支持王红元老师。

认识axios

为什么选择axios? 因为作者推荐。

功能特点:

  • 在浏览器中发送 XMLHttpRequests 请求
  • 在 node.js 中发送 http请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 等等

补充:axios名称的由来? 个人理解,没有具体的翻译,axios:ajax i/o system.

axios请求方式

支持多种请求方式:

  • axios(config)
  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

axios的基本使用

import axios from 'axios'

// axios就是axios的默认实例对象
// get请求
axios.get('http://123.207.32.32:8000/home/multidata').then((res) => {
  // Promise内部指定了返回值res是Axiosresponse类型
  console.log(res.data)
})

// 额外补充的Promise中类型的使用
// Promise本身是可以有类型
new Promise<string>((resolve) => {
  // 泛型指定了,只能传string
  resolve('abc')
}).then((res) => {
  // 并且res也是string类型的
  console.log(res.length)
}

// 使用http://httpbin.org模拟数据请求

// get请求,并且传入参数
axios
  .get('http://httpbin.org/get', {
    // get请求使用params传参,并且最后会拼接到url后面
    params: {
      name: 'coderwhy',
      age: 18
    }
  })
  .then((res) => {
    console.log(res.data)
  })

// post请求,传入参数
axios
  .post('http://httpbin.org/post', {
    // post请求使用data传参
    data: {
      name: 'why',
      age: 18
    }
  })
  .then((res) => {
    console.log(res.data)
  })

有时候,我们可能需要同时发送两个请求,使用axios.all(), 可以放入多个请求的数组,当所有请求完成之后,axios.all() 会返回一个数组,axios.all()内部使用的其实就是Promise.all()

// axios.all -> 多个请求, 一起返回
axios
  .all([
    axios.get('/get', { params: { name: 'why', age: 18 } }),
    axios.post('/post', { data: { name: 'why', age: 18 } })
  ])
  .then((res) => {
    // 结果是个数组
    console.log(res[0].data)
    console.log(res[1].data)
  })

AxiosRequestConfig

AxiosRequestConfig选项是创建axios实例或者进行request请求的时候传入的参数。

解释 配置选项
请求地址 url: '/user'
请求类型 method: 'get'
请求根路径 baseURL: 'www.mt.com/api'
请求前的数据处理 transformRequest:[function(data){}]
请求后的数据处理 transformResponse: [function(data){}]
自定义的请求头 headers:{'x-Requested-With':'XMLHttpRequest'}
URL查询对象 params:{ id: 12 }
查询对象序列化函数 paramsSerializer: function(params){ }
request body data: { key: 'aa'}
超时设置 timeout: 1000
跨域是否带Token withCredentials: false
自定义请求处理 adapter: function(resolve, reject, config){}
身份验证信息 auth: { uname: '', pwd: '12'}
响应的数据格式(json、blob、document、arraybuffer、text、stream) responseType: 'json'
// axios的配置选项
// 全局的配置 baseURL timeout headers
axios.defaults.baseURL = 'http://httpbin.org'
axios.defaults.timeout = 10000
// axios.defaults.headers = {}

// 每一个请求单独的配置 timeout headers
axios
  .get('/get', {
    params: {
      name: 'coderwhy',
      age: 18
    },
    // 单独配置
    timeout: 5000,
    headers: {}
  })
  .then((res) => {
    console.log(res.data)
  })

// post请求
axios
  // 全局配置baseURL之后就不用再写url了
  .post('/post', {
    data: {
      name: 'why',
      age: 18
    }
  })
  .then((res) => {
    console.log(res.data)
  })

axios实例的拦截器

为什么要创建axios的实例呢? 当我们从axios模块中导入对象时,使用的实例是默认的实例,当给该实例设置一些默认配置时,这些配置就被固定下来了。但是后续开发中,某些配置可能会不太一样,比如某些请求需要使用特定的baseURL或者timeout或者content-Type等,这个时候, 我们就可以创建新的实例,并且传入属于该实例的配置信息。

axios也可以设置拦截器,拦截每次请求和响应:

axios.interceptors.request.use(请求成功拦截函数, 请求失败拦截函数)
axios.interceptors.response.use(响应成功拦截函数, 响应失败拦截函数)
  • use函数有两个参数,第一个是成功执行的函数,第二个是失败执行的函数
  • 请求的拦截会在请求之前执行,响应的拦截会在响应之后执行,具体如下图:

更多关于axios拦截器执行流程分析,可见:blog.****.net/m0_64813329…

// axios的拦截器
// 参数fn1: 请求发送成功会执行的函数
// 参数fn2: 请求发送失败会执行的函数
axios.interceptors.request.use(
  (config) => {
    // 想做的一些操作
    // 1.给请求添加token
    // 2.添加isLoading动画
    console.log('请求成功的拦截')
    return config
  },
  (err) => {
    console.log('请求发送错误')
    return err
  }
)

// fn1: 数据响应成功(服务器正常的返回了数据 200)
// fn2: 数据响应失败
axios.interceptors.response.use(
  (res) => {
    console.log('响应成功的拦截')
    return res
  },
  (err) => {
    console.log('服务器响应失败')
    return err
  }
)

封装axios

先说一下我们封装要达到的目的:可以对某个请求、某个请求实例的所有请求、所有请求实例的所有请求,设置拦截和是否显示loading。

先看一下目录结构,config.js里面是环境配置,type.js里面是类型配置,request/index.js里面返回一个请求类,外面的index.js文件返回一个请求的new实例供外界使用。

下面就对每个文件的代码以及作用进行讲解:

config.ts代码如下,会根据环境配置不同的BASE_URL。

// 根据process.env.NODE_ENV区分
// 开发环境: development
// 生成环境: production
// 测试环境: test

let BASE_URL = ''
const TIME_OUT = 10000

if (process.env.NODE_ENV === 'development') {
  BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = 'http://coderwhy.org/prod'
} else {
  BASE_URL = 'http://coderwhy.org/test'
}

export { BASE_URL, TIME_OUT }

在type.js里面我们定义一个接口,用于规定创建请求实例或者调用request方法的时候传入的参数是什么样的,代码如下:

import type { AxiosRequestConfig, AxiosResponse } from 'axios'

// 定义一个接口,表示这个接口的实例要有这4个属性,当然不是必须的,是可选的
// 传入一个泛型,默认值是AxiosResponse
export interface HYRequestInterceptors<T = AxiosResponse> {
  // 拦截器都是可选的
  // 请求拦截
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
  // 请求错误拦截
  requestInterceptorCatch?: (error: any) => any
  // 响应拦截
  // 由于我们在前面直接将res.data返回了,所以这里如果传入了T,那么返回的类型就是传入的T
  responseInterceptor?: (res: T) => T
  // 响应错误拦截
  responseInterceptorCatch?: (error: any) => any
}

// 定义一个新的接口,继承于AxiosRequestConfig,表示我们传入的参数要有interceptors和showLoading,当然也是可选的
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  // 对原来的AxiosRequestConfig进行扩展,添加拦截器和是否显示loading,可选的
  interceptors?: HYRequestInterceptors<T>
  showLoading?: boolean
}

核心代码就是request文件夹下的index.js文件,代码如下:

import axios from 'axios'
// 导入axios实例的类型
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'

// 引入loading组件
import { ElLoading } from 'element-plus'
// 引入loading组件的类型
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'

// 默认显示loading
const DEAFULT_LOADING = true

class HYRequest {
  // axios实例
  instance: AxiosInstance
  // 当前请求实例的拦截器
  interceptors?: HYRequestInterceptors
  // 是否显示loading
  showLoading: boolean
  // 保存的loading实例
  loading?: ILoadingInstance

  constructor(config: HYRequestConfig) {
    // 创建axios实例
    this.instance = axios.create(config)
    // 保存基本信息
    this.interceptors = config.interceptors
    this.showLoading = config.showLoading ?? DEAFULT_LOADING

    // 使用拦截器
    // 1.从config中取出的拦截器是对应的实例的拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )

    // 2.添加所有的实例都有的拦截器
    // 请求的时候,先添加的拦截器后执行
    // 响应的时候,先添加的拦截器先执行
    this.instance.interceptors.request.use(
      (config) => {
        console.log('所有的实例都有的拦截器: 请求成功拦截')

        // 所有的请求都添加loading
        if (this.showLoading) {
          // 添加loading
          this.loading = ElLoading.service({
            lock: true,
            text: '正在请求数据....',
            background: 'rgba(0, 0, 0, 0.5)'
          })
        }
        return config
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 请求失败拦截')
        return err
      }
    )

    this.instance.interceptors.response.use(
      (res) => {
        console.log('所有的实例都有的拦截器: 响应成功拦截')
        // 所有的请求,将loading移除
        this.loading?.close()

        // 因为我们需要的就是res.data,所以我们可以在所有请求实例的请求的响应拦截器里面,直接把res.data返回,这样我们就可以直接使用了
        const data = res.data
        // 判断当HttpErrorCode是200的时候,服务端和客户端一块自定义的错误信息
        if (data.returnCode === '-1001') {
          console.log('请求失败~, 错误信息')
        } else {
          return data
        }
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 响应失败拦截')
        // 所有的请求,将loading移除
        this.loading?.close()

        // 判断不同的HttpErrorCode显示不同的错误信息
        if (err.response.status === 404) {
          console.log('404的错误~')
        }
        return err
      }
    )
  }

  // 1.传入返回结果的类型T,这样在Promise中我们就知道返回值的类型是T了
  // 2.通过HYRequestConfig<T>,将返回值类型T告诉接口,从而在接口的返回响应拦截中指明返回值类型就是T
  request<T>(config: HYRequestConfig<T>): Promise<T> {
    // 返回一个Promise对象,好让使用者在外面拿到数据
    return new Promise((resolve, reject) => {
      // 1.单个请求对请求config的处理
      if (config.interceptors?.requestInterceptor) {
        // 如果有单个请求的拦截器,就执行一下这个函数,然后返回
        config = config.interceptors.requestInterceptor(config)
      }

      // 2.判断单个请求是否需要显示loading
      if (config.showLoading === false) {
        this.showLoading = config.showLoading
      }
      
      // 注意:前面我们添加的所有请求拦截器就是在这里执行的

      this.instance
        // request里面有两个泛型,第一个泛型默认是any,第二个泛型是AxiosResponse
        // 由于前面我们已经将res.data直接返回了,所以其实最后的数据就是T类型的,所以我们在第二个泛型中要指定返回值的类型T
        .request<any, T>(config)
        .then((res) => {
          // 1.单个请求对数据的处理
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          // 2.将showLoading设置true, 这样不会影响下一个请求
          this.showLoading = DEAFULT_LOADING

          // 在请求完成以后,我们执行一下当前请求对应的响应拦截器,然后再将结果返回
          
          // 3.将结果resolve返回出去
          resolve(res)
        })
        .catch((err) => {
          // 将showLoading设置true, 这样不会影响下一个请求
          this.showLoading = DEAFULT_LOADING
          reject(err)
          return err
        })
    })
  }

  get<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }

  post<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }

  delete<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }

  patch<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PATCH' })
  }
}

export default HYRequest

这时候我们需要创建一个请求实例,用于发送网络请求,当然我们也可以创建不止一个请求实例,然后设置不同的baseurl、超时时间、拦截器等等,这里我们只创建一个,所以外层的index.ts代码如下:

// service统一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'

// 创建一个新的请求,并传入参数
const hyRequest = new HYRequest({
  // 传入baseurl
  baseURL: BASE_URL,
  // 传入超时时间
  timeout: TIME_OUT,
  // 传入拦截器
  interceptors: {
    requestInterceptor: (config) => {
      // 给当前请求实例所有的请求添加token
      const token = ''
      if (token) {
        // 模板字符串进行拼接
        config.headers.Authorization = `Bearer ${token}`
      }

      console.log('请求成功的拦截')
      return config
    },
    requestInterceptorCatch: (err) => {
      console.log('请求失败的拦截')
      return err
    },
    responseInterceptor: (res) => {
      console.log('响应成功的拦截')
      return res
    },
    responseInterceptorCatch: (err) => {
      console.log('响应失败的拦截')
      return err
    }
  }
})

export default hyRequest

在main.ts中使用如下:

import { createApp } from 'vue'
import App from './App.vue'
// 导入请求实例
import hyRequest from './service'

const app = createApp(App)
app.mount('#app')

hyRequest.request({
  url: '/home/multidata',
  method: 'GET',
  headers: {},
  interceptors: {
    requestInterceptor: (config) => {
      console.log('单独请求的config')
      config.headers['token'] = '123'
      return config
    },
    responseInterceptor: (res) => {
      console.log('单独响应的response')
      return res
    }
  }
})

// 定义返回结果的类型,因为返回的类型只有我们知道
interface DataType {
  data: any
  returnCode: string
  success: boolean
}

// 只有请求者才知道返回结果的类型
hyRequest
  .get<DataType>({
    url: '/home/multidata',
    showLoading: false
  })
  // 这时候这里的res就是DataType类型的
  .then((res) => {
    console.log(res.data)
    console.log(res.returnCode)
    console.log(res.success)
  })

总结

  1. 创建axios实例的时候在constructor构造器里面就传入拦截器,然后挂载到axios实例上面,达到给axios实例添加拦截器的效果
  2. 如果不管axios实例有没有拦截器都添加拦截器,达到所有axios实例都有拦截器的效果
  3. 在当前请求的request方法执行之前,执行一下当前请求的请求拦截器,达到给单个请求添加请求拦截器的效果
  4. request请求发出之后,我们在Promise的.then里面执行一下当前请求的响应拦截器,然后再将结果resolve出去,就达到了单个请求添加响应拦截器的效果