基于 TS 实现 axios(八)

本文主要介绍了如何使用 TypeScript 对 axios 进行更深入的配置,包括设置 `withCredentials` 开启跨域携带 cookie,防止 XSRF 攻击,添加下载和上传监控,整理核心库 xhr 的逻辑,自定义 HTTP auth,设定合法状态码,以及自定义 URL 参数解析规则和 baseURL 的使用。内容详细解释了每个配置项的作用和实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一章主要完善接口
目录结构没有进行修改

withCredentials

这个功能是可以携带跨越请求的,默认情况下是自动携带同源 cookie 的 ,但是跨域的时候是不可以进行携带的,将 withCredentials 设置成 true 就可以进行携带了。

types/index.ts

export interface AxiosRequestConfig {
	// ...

    withCredentials?:boolean,
	
    //...
}

core/xhr.ts

// ...
if(timeout) request.timeout = timeout;

if(withCredentials) {
    request.withCredentials = withCredentials;
}
// ...

接改变这点就够了

XSRF

其实就是在 header 中添加 token (后端生成返回的标识)

types/index.ts

export interface AxiosRequestConfig {
	// ...
    xsrfCookieName?:string,
    xsrfHeaderName?:string,
	// ...
}

defaults.ts

const defaults: AxiosRequestConfig = {
	// ...
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
	// ...
}

helpers/url.ts

interface URLOrigin{
    protocol:string,
    host: string,
}


export function isURLsameOrigin(requestURL: string):boolean {
    const parseOrigin = resolverURL(requestURL);
    return (parseOrigin.protocol === currentOrigin.protocol && parseOrigin.host === currentOrigin.host)

}

const urlParsingNode = document.createElement('a');
const currentOrigin = resolverURL(window.location.href);


function resolverURL(url: string): URLOrigin {
    urlParsingNode.setAttribute('href',url);

    const {protocol,host} = urlParsingNode;

    return {
        protocol,
        host,
    }
}

通过 a 标签拿到 protocol (协议)和 host (主机地址)

新建 helpers/cookie.ts

const cookie = {
    read(name: string) : string | null {
        const match = document.cookie.match(new RegExp('(^|;\\s*)(' +  name + ')=([^;]*)'));
        return match ? decodeURIComponent(match[3]) : null;
    }
}

export default cookie;

读取 cookie

core/xhr.ts

// ...
request.ontimeout = function handleTimeout() {
    reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}
// 添加的部分
if((withCredentials || isURLsameOrigin(url)) && xsrfCookieName){
    const xsrfValue = cookie.read(xsrfCookieName);
    if(xsrfValue && xsrfHeaderName) {
        headers[xsrfHeaderName] = xsrfValue;
    }
};
// 添加的部分

// ...

自动设置 cokie ,个人感觉没什么用

下载 和 上传监控

types/index.ts

// ...
export interface AxiosRequestConfig {
	// ...

    onDownloadProgress?:(e: ProgressEvent) => void,
    onUploadProgress?: (e: ProgressEvent) => void,
	// ...
}
// ...

helpers/util.ts

export function isFormData(val: any): val is FormData {
    return typeof val !== 'undefined' && val instanceof FormData
}

判断对象是否是 FormData

core/xhr.ts

// ... 锚点
request.ontimeout = function handleTimeout() {
    reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}

// 添加的部分
if(onDownloadProgress) {
    request.onprogress = onDownloadProgress;
}        

if(onUploadProgress) {
    request.upload.onprogress = onUploadProgress;
}

if(isFormData(data)) {
    delete headers['Content-Type']
} 
// 添加的部分

// ...

将 上传 和 下载的函数挂载到 request 上;如果 dataFormdata 类型的数据就将 Content-Type 删除,浏览器会默认添加的

整理核心库 xhr

import {AxiosRequestConfig,AxiosPromise,AxiosResponse} from '../types';
import {parseHeaders} from '../helpers/header';
import {createError} from '../helpers/error';
import {isURLsameOrigin} from '../helpers/url';
import {isFormData} from '../helpers/util'
import cookie from '../helpers/cookie';

export default function xhr(config:AxiosRequestConfig) : AxiosPromise {
    return new Promise((reslove,reject)=> {
        const {
            data = null,
            url,
            method='get',
            timeout,
            headers,
            responseType,
            cancleToken,
            withCredentials,
            xsrfCookieName,
            xsrfHeaderName,
            onDownloadProgress,
            onUploadProgress
        } = config;
        const request = new XMLHttpRequest();
        
        request.open(method.toUpperCase(),url!,true);

        configureRequest();

        addEvents();

        processHeaders();

        proccessCancel();

        request.send(data);


        function configureRequest(): void{
            if(responseType) request.responseType = responseType;

            // 超时操作
            if(timeout) request.timeout = timeout;
        
            if(withCredentials) request.withCredentials = withCredentials;
        }

        function addEvents():void{
            
            request.onreadystatechange = function handleLoad() {
                if(request.readyState !== 4) {
                    return;
                }

                if(request.status === 0) {
                    // 网络错误和超时错误时 status为 0
                    return;
                }

                const responseHeaders = parseHeaders(request.getAllResponseHeaders());
                const responseDate = responseType !== 'text' ? request.response : request.responseText;
                const response:AxiosResponse = {
                    data:responseDate,
                    status:request.status,
                    statusText: request.statusText,
                    headers: responseHeaders,
                    config,
                    request,
                }
                handleResponse(response);
            }
 
            // 请求错误
            request.onerror = function handleError() {
                reject(createError('Network Error',config,null,request));
            } 
            // 超时错误
            request.ontimeout = function handleTimeout() {
                reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
            }

            if(onDownloadProgress) {
                request.onprogress = onDownloadProgress;
            }        

            if(onUploadProgress) {
                request.upload.onprogress = onUploadProgress;
            }
        }

        function processHeaders():void {
            if(isFormData(data)) {
                delete headers['Content-Type']
            } 
    
            if((withCredentials || isURLsameOrigin(url!)) && xsrfCookieName){
                console.log('执行了',xsrfCookieName);
                const xsrfValue = cookie.read(xsrfCookieName);
                console.log(xsrfValue);
                if(xsrfValue && xsrfHeaderName) {
                    headers[xsrfHeaderName] = xsrfValue;
                }
            };
    
            // 错误结束        
            Object.keys(headers).forEach((name) => {
                if(data === null && name.toLocaleLowerCase() === 'content-type') {
                    delete headers[name];
                } else {
                    request.setRequestHeader(name,headers[name]);
                }
            })
        }

        function proccessCancel():void {
            if(cancleToken) {
                cancleToken.promise.then(reason => {
                    request.abort()
                    reject(reason)
                })
            }
        }


        function handleResponse(response: AxiosResponse) : void{
            if(response.status >= 200 && response.status < 300) {
                reslove(response);
            } else {
                reject(createError(`Request failed with status code ${response.status}`,config,null,request,response));
            }
        }
    })

}

从以上代码可以看出,我将以前的代码简单的归了类, 依次执行 configureRequestaddEventsprocessHeadersproccessCancel 这四个函数,组合在进行发送

HTTP auth

HTTP协议中的 Authorization 请求消息头含有服务器用于验证用户代理身份的凭证,通常会在服务器返回401 Unauthorized 状态码以及WWW-Authenticate 消息头之后在后续请求中发送此消息头。

axios 中可以通过 auth 来设置 Authorization ,个人感觉没太大用。

types/index.ts

// ...
export interface AxiosRequestConfig {
    // ...
    auth?:AxiosBasicCredentials,
    // ...
}
// ...
export interface AxiosBasicCredentials {
    username: string,
    password: string,
}

core/xhr.ts

if(auth) {
    headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
}

就这样就行了,不过使用的话还是要配合后台的。

自定义合法状态码

我们默认的合法状态码( status )是 200 - 300,我们可以通过 validateStatus 函数来自定义合法状态

types/index.ts

export interface AxiosRequestConfig {
	// ...
    validateStatus?:(status:number) => boolean,
	// ...
}

core/xhr.ts

function processHeaders():void {
    // ...
    if(auth) {
        headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
    }

    // ...
}

使用

axios.get('/more/304',{
  validateStatus(status){
    return status >= 200 && status < 400
  }
}).then(res => {
  console.log(res)
}).catch((e) =>{
  console.log(e.message);
})

自定义参数解析规则

看名字应该理解,用户自己定义 url 产生的解析规则

types/index.ts

export interface AxiosRequestConfig {
	// ...
    paramsSerializer?:(params: any) => string,
	// ...
}

helpers/url.ts

export function buildURL(url: string, params?: any, paramsSerializer?:(params:any) => string):string {
    if(!params) return url;

    let serializedParams;

    if(paramsSerializer) {
        serializedParams = paramsSerializer(params);
    } else if(isURLSearchParams(params)){
        serializedParams = params.toString()
    }else {
        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)}`);
            })
        })
        serializedParams = parts.join('&');
    }

    if(serializedParams) {
        const markIndex = url.indexOf('#');
        if(markIndex !== -1) {
            url = url.slice(0,markIndex);
        }
        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;

    }
    return url;
}

这个函数分三种情况:paramsSerializer 有的话,就进行自定义的参数解析; params 是否是 URLSearchParams ;默认解析情况

helpers/util.ts

export function isURLSearchParams(val: any): val is URLSearchParams{
    return typeof val !== 'undefined' && val instanceof URLSearchParams
}

core/dispatchRequest.ts

function transformURL (config: AxiosRequestConfig) :string {
    const {url,params,paramsSerializer} = config;
    return buildURL(url!,params,paramsSerializer);
}

baseURL

就是我们访问同一个域名下的多个接口时,我们不希望每次发送请求都填写完整的 url,可以用这个参数进行配合

types/index.ts

export interface AxiosRequestConfig {
    baseURL?:string,
}

helpers/url.ts

export function isAbsoluteURL(url:string): boolean {
    return /(^[a-z][a-z\d\+\-\.]*:])?\/\//i.test(url)
}

export function combineURL(baseURL:string, relativeURL?:string):string {
    return relativeURL ? baseURL.replace(/\/+$/,'') + '/' +relativeURL.replace(/^\/+/, ''): baseURL
}

前一个函数用来判断是否是绝对url,后一个函数进行url地址连接

core/dispatchRequest.ts

function transformURL (config: AxiosRequestConfig) :string {
    let {url,params,paramsSerializer,baseURL} = config;
    if(baseURL && !isAbsoluteURL(url)) {
        url = combineURL(baseURL,url);
    }
    return buildURL(url!,params,paramsSerializer);
}

这里进行完整的url地址拼接

我觉得这些函数已经够用了,其他的一些很小众的函数我就不写了。

### 使用 TypeScript 封装 Axios 为了简化 HTTP 请求处理并提高项目的可维护性和类型安全性,在项目中可以创建一个自定义的 Axios 实例[^1]。 #### 创建 Axios 实例 通过 `axios.create` 方法来初始化一个新的 Axios 实例,并配置基础 URL 和其他默认设置: ```typescript import axios, { AxiosInstance } from 'axios'; const apiClient: AxiosInstance = axios.create({ baseURL: 'https://api.example.com/v1', timeout: 5000, }); ``` 这使得每次调用 API 接口时无需重复指定相同的参数,同时也方便全局修改这些选项。 #### 添加请求和响应拦截器 利用 Axios 提供的拦截功能可以在请求发出前或接收到响应后执行特定逻辑。例如,可以在请求头中自动加入认证令牌,或者统一处理错误消息[^2]。 ```typescript // 请求拦截器:用于添加 token 或显示加载状态 apiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error), ); // 响应拦截器:集中管理异常情况下的提示信息 apiClient.interceptors.response.use( response => response.data, error => { if (!error.response) { throw new Error('网络连接失败,请稍后再试!'); } switch (error.response.status) { case 401: throw new Error('未授权访问,请登录后重试!'); default: throw new Error(`服务器发生错误(${error.response.statusText})`); } }, ); ``` 上述代码片段展示了如何在发送请求之前检查是否存在有效的身份验证令牌以及如何优雅地捕获并解释来自服务器的不同类型的错误反馈。 #### 定义服务接口函数 最后一步是基于这个增强过的 Axios 实例构建具体的服务方法,以便于业务层直接调用而不需要关心底层实现细节: ```typescript interface UserResponseData { id?: number; name: string; } class UserService { static async fetchUser(userId: number): Promise<UserResponseData> { try { const result = await apiClient.get(`/users/${userId}`); return result as unknown as UserResponseData; } catch (e) { console.error(e.message); // 自定义错误处理方式 throw e; } } } ``` 这样不仅提高了代码复用率还增强了系统的健壮性,因为所有的 HTTP 调用都被集中在一处管理和控制之下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值