axios的二次封装

455 阅读5分钟

前言

在工作中新接手了一个项目,前端后台都是刚接手,代码都未细看就开始开发了,在前后端约定传递参数格式时,后端希望前端传递的格式中有一个参数是数组。如下:

const params = {
    name: 'xxx',
    lists: [ 'aaa', 'bbb']
}

于是我们两就哼哧哼哧的写完,结果一联调,后端发现接收到的数组数据格式不符合预期,去查看代码发现是因为在项目中,axios封装中,数据请求格式为application/x-www-form-urlencoded

axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'

同时也引入了qs统一做序列化的处理

import qs from 'qs'

qs.stringify(params)

经过序列化后的数组格式类似是:

lists[0]='aaa'&list[1]='bbb'

现项目的后端没有写相关的接收数据处理,所以我们就细看代码,发现之前的接口,对于数组都是转成字符串的形式传递

const params = {
    name: 'xxx',
    lists: 'aaa,bbb'
}

于是我们就愉快的决定这样传递快速的解决问题。

虽然项目中解决了问题,需求完成了,但是对于这一块的知识我是完全不清楚,于是去查资料且写下此文章记录一下。

axios发送请求的数据格式

Content-Type

Content-Type编码类型,是指http、https发送信息至服务器时的内容编码类型,用于表明发送数据流的类型,服务器根据编码类型使用特定的解析方式,获取数据流中的数据。

常用的Content-Type有如下:

text/html,text/plain,text/css,text/javascript,image/jpeg,image/png,image/gif等常见的页面资源类型。

application/x-www-form-urlencoded,multipart/form-data,application/json,application/xml,这四个是表单提交或上传文件的常用的资源类型。

axios的常见的数据格式(content-type)

  • application/json: 请求体中的数据会以json字符串的形式发送到后端
  • application/x-www-form-urlencoded:请求体中的数据会以普通表单形式(键值对)发送到后端
  • multipart/form-data: 它会将请求体的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。

axios默认的请求头content-type类型是application/json

说明:由于公司电脑不能截图,请求在浏览器Network上的截图借用的网上的图(ps:希望以后自己可以模拟出来),借用的图的来源链接会在文中说明。

application/json

如果前后端约定的入参是json,那么不需要做任何处理。

json格式的get请求的编码方式是:Query String Parameters

一.png

json格式的get请求的编码方式是:Request Payload

二.png

application/x-www-form-urlencoded

如果前后端约定的入参格式是application/x-www-form-urlencoded ,则编码方式是编码方式formdata( 表单格式数据提交),用这种方式要把json {name:lisi,age:4}格式转换为name=lisi&age=4的格式

三.png

所以数据处理方法如下:

一:利用qs将参数转换为query参数

引入:

import axios from 'axios'
import qs from 'qs'

使用示例:

let data = {
    name: 'lisi',
    age: '4'
}
axios({
        headers: {'Content-Type':'application/x-www-form-urlencoded'},
        method: 'post',
        url: url,
        data: Qs.stringify(data)
    })
qs的说明:

1.将url中的参数转为对象;2.将对象转为url参数形式。

qs处理数组

qs.stringify({ids: [1, 2, 3]}, { indices: false })
//  ids=1&ids=2&ids=3

qs.stringify({ids: [1, 2, 3]}, { indices: true })
//  ids[0]=1&ids[1]=2&id[2]=3

qs.stringify({ids: [1, 2, 3]}, {arrayFormat: ‘repeat‘}) 
// ids=1&ids=2&ids=3

qs.stringify({ids: [1, 2, 3]}, {arrayFormat: ‘brackets‘})
 // ids[]=1&ids[]=2&ids[]=3

qs.stringify({ids: [1, 2, 3]}, {arrayFormat: ‘indices‘})
// ids[0]=1&ids[1]=2&ids[2]=3

qs.stringify({ids: [1, 2, 3]}, {arrayFormat: ‘comma‘}) 
// ids[0]=1&ids[1]=2&ids[2]=3
二:用 URLSearchParams 传递参数
let param = new URLSearchParams()
param.append('name', 'lisi')
param.append('age', 4)
axios({
    method: 'post',
    url: url,
    data: param
})

当传递的是数组的时候,没有做处理之前,会呈现为 list:[object object],[object object]。

将数组转换成JSON字符串可以解决此问题:

let param = new URLSearchParams()
param.append('name', 'lisi')
param.append('age', 4)
param.append('list', JSON.stringify([{name: 'lisi', age: 4}]))
axios({
    method: 'post',
    url: url,
    data: param
})

PS:qs.stringify()和JSON.stringify的区别:
{name: 'lisi',age: 4}
qs.stringify()转化为name="lisi"&age=4
JSON.stringify转化为{"name": "lisi","age":"4"}

multipart/form-data

表单数据中,包含有上传图片、上传视频音频等。此类上传表单,只能是Content-Type:multipart/form-data,以Form Data编码方式传送数据。

四.png

参考网站:
axios 发送数据请求的方式

axios的二次封装

在vue项目中,与后台交互获取数据,一般采用的是axios库。

什么是axios?

axios官网

Axios是一个基于promise用于浏览器和node.js的简单HTTP客户端。Axios在一个具有可扩展接口的很小体积的JS包中提供了一个易于使用的库。

特点:

  1. 从浏览器中创建 XMLHttpRequest
  2. 从 node.js 发出 http 请求
  3. 支持 Promise API
  4. 拦截请求和响应
  5. 转换请求和响应数据
  6. 取消请求
  7. 自动转换JSON数据
  8. 客户端支持防止 CSRF/XSRF

安装:

npm install axios;

axios.ts

import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"

interface Result {
    code: number;
    msg: string;
}
// T = any 泛型参数默认类型
interface ResultData<T = any> extends Result{
    data?: T
}
const URL:string = '/mock'

// 状态码一般是前后端约定
enum RequestEnums {
    TIMEOUT = 20000, // 超时
    OVERDUE = 600, // 过期
    FAIL = 999, // 失败
    SUCCESS = 200 // 成功
}
const config = {
    baseURL: URL as string,
    timeout: RequestEnums.TIMEOUT as number,
    // 跨域时允许携带凭证
    withCredentials: true
}

class RequestHttp {
    protected instance: AxiosInstance;

    public constructor(config: AxiosRequestConfig){
        this.instance = axios.create(config)
        this.initRequestInterceptor()
        this.initResponseInterceptor()
    }
    /**
     * 请求拦截器
     * 客户端发送请求--》请求拦截器--》服务器
     * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
     */
    private initRequestInterceptor = () => {
        this.instance.interceptors.request.use(this.handleRequest)
    }
    private handleRequest = (config: AxiosRequestConfig) => {
        // 存储在本地
        const token = localStorage.getItem('token') || '';
        return {
            ...config,
            headers: {
                'Authorization': token
            }
        }
    }
    /**
     * 响应拦截器
     * 服务器返回信息 --》拦截,统一处理 --》客户端js获取信息
     */
    private initResponseInterceptor = () => {
        this.instance.interceptors.response.use((response: AxiosResponse) => {
            // 解构
            const {data, config} = response
            if(data.code && data.code === RequestEnums.OVERDUE){
                // 登录信息失效,应跳转到登录页面,并清空本地的token
                localStorage.setItem('token', '');
                // router.replace({
                //   path: '/login'
                // })
                return Promise.reject(data);
            }
            if(data.code && data.code !== RequestEnums.SUCCESS){
                // 返回错误
                return Promise.reject(data)
            }
            return data
        },(error: AxiosError) => {
            const { response } = error
            if(response){
                this.handleCode(response.status)
            }
            // 处理404
        })
    }
    private handleCode(code: number): void {
        switch(code){
            case 401:
                console.log('登陆失败,请重新登录')
                break
            default:
                console.log('请求失败')
                break

        }
    }

    // 常用方法封装
    get<T>(url: string, params?: object): Promise<ResultData<T>>{
        return this.instance.get(url, params)
    }
    post<T>(url: string, params?: object): Promise<ResultData<T>>{
        return this.instance.post(url, params)
    }
    put<T>(url: string, params?: object): Promise<ResultData<T>>{
        return this.instance.put(url, params)
    }
    delete<T>(url: string, params?: object): Promise<ResultData<T>>{
        return this.instance.delete(url, params)
    }
}

// 导出一个实例对象
export default new RequestHttp(config)

api->login.ts

import axios from "../plugins/axios";

namespace Login{
    // 用户登陆表单
    export interface LoginReqForm{
        username: string;
        password: string;
    }
    export interface LoginResData{
        token: string;
    }
}

// 用户登录
export const login = (params: Login.LoginReqForm) => {
    return axios.post<Login.LoginResData>('/user/login', params)
}

页面应用

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import { login } from './../api/login'
export default defineComponent({
    name: 'Cart',
    components: {
    },
    setup(){
        const loginForm = {
            username: 'test',
            password: 'test'
        }
        const loginFun = async () => {
            const data = await login(loginForm)
            console.log('data', data)
        } 
        return { loginFun }
    }
})
</script>
<template>
<div>
    cart页面
    <router-link class="btn btn-primary btn-lg btn-block" to="/checkout">Continue to checkout</router-link>
    <button @click="loginFun">测试接口</button>
</div>
</template>