基於 TS 實現 axios(二)

這一次主要是基礎功能實現

目錄結構:僅僅包含 src 下的編碼

在這裏插入圖片描述

注:get 中的參數是在 url地址上的;post 中的參數是在 data 中的,並不能在 url 地址中

url 編碼

實現了這個功能其實就意味着實現了 GET ,想要實現 POST 就必須加上響應頭 header

要想傳遞請求,我們必須將 URL地址參數 按照一定規則拼接起來,不多說,先上代碼:

// 判斷類型是否爲:日期
function isDate(val: any):val is Date {
    return toString.call(val) === '[object Date]';
}

// 判斷類型是否爲:對象
function isObject(val: any):val is Object {
    return val != null && toString.call(val) === '[object Object]';
}

// 使用 encode 將字符串轉換爲 url編碼,保留特殊字符
encode(val: string) : string {
    return encodeURIComponent(val)
    .replace(/%40/g,'@')
    .replace(/%3A/ig,':')
    .replace(/%24/g,'$')
    .replace(/%2C/ig,',')
    .replace(/%20/g,'+')
    .replace(/%5B/ig,'[')
    .replace(/%5D/g,']')
}

// 主函數:拼接 url 和 參數,並把處理過後的地址返回
function buildURL(url: string, params?: any):string {
    if(!params) return url;

    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(isObject(val)) {
                val = JSON.stringify(val);
            }
            parts.push(`${encode(key)}=${encode(val)}`);
        })
    })
    let serializedParams = parts.join('&');
    if(serializedParams) {
        const markIndex = url.indexOf('#');
        if(markIndex !== -1) {
            url = url.slice(0,markIndex);
        }
        
        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;

    }
    return url;
}


規則:

  • params 中每一項中,屬性名與數據之間使用 = 進行連接,項與項之間使用 & 進行連接,url 地址 與 params (參數) 使用 ? 進行連接
  • 在進行數組拼接時,屬性名後面加上[],例如:foo: ['bar', 'baz'] 就會轉換爲 foo[]=bar&foo[]=baz
  • 在進行對象類型拼接,把對象按照 JSON.stringify 轉換字符串,而後進行拼接
  • 處理日期的時候,使用 .toISOString 將其轉換爲字符串,進行連接
  • 如果你的 url 地址中出現了 # (hash 值)時,會忽略 # 後的地址(包含 # 號),
  • 字符串中還要容納一些特殊字符:@:$,(空格)

注:字符串中的空格轉換爲 +

header

這裏的 header 表示的是 請求頭

我們使用 axios 知道,header 是一個對象,我們可以使用 .setRequestHeader 來進行設置header的參數,但在之前需要進行一些處理:

// 主要是將一些不規範的寫法規範化,例如:'content-type' 必須是 'Content-Type' 纔是有效的
function normalizeHeaderName(headers: any, normalizedName:string):void {
    if(!headers) {
        return
    }
    Object.keys(headers).forEach((name) => {
        if(name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()){
            headers[normalizedName] = headers[name];
            delete headers[name];
        }
    })
}

function processHeaders (headers: any, data: any) :any {
    normalizeHeaderName(headers, 'Content-Type');

    if(isPlainObject(data)) {
        if(headers && headers['Content-Type']) {
            headers['Content-Type'] = 'application/json;charset=utf-8';
        }
    }

    return headers;
}

Content-Type默認是 text/plain;charset=UTF-8,如果你的 data 使用的是 json 格式的話,Content-Type 就應該是 application/json;charset=UTF-8,否則服務器那邊是接收不到的

promise

應該都瞭解 axios 中獲取數據通過 promise 的 then,處理錯誤信息通過 promise 的 catch,如下:

import {AxiosRequestConfig,AxiosPromise,AxiosResponse} from './types';
import {parseHeaders} from './helpers/header';
function xhr(config:AxiosRequestConfig) : AxiosPromise {
    return new Promise((reslove,reject)=> {
        const {data = null,url,method='get',params,headers,responseType} = config;
        const request = new XMLHttpRequest();
        
        if(responseType) request.responseType = responseType;

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

            const responseHeaders = request.getAllResponseHeaders();
            const responseDate = responseType !== 'text' ? request.response : request.responseText;
            const response:AxiosPromise = {
                data:responseDate,
                status:request.status,
                statusText: request.statusText,
                headers: parseHeaders(responseHeaders),
                config,
                request,
            }
            reslove(response);
        }

        request.open(method.toUpperCase(),url,true);

        Object.keys(headers).forEach((name) => {
            if(data === null && name.toLocaleLowerCase() === 'content-type') {
                delete headers[name];
            }
            request.setRequestHeader(name,headers[name]);
        })

        request.send(data);
    })

}

使用 Promise 中的 resolve 進行傳遞,這個還沒寫 error (異常),返回一個對象,大概就這樣了

好了,看一下全部代碼吧!

全部代碼

helper目錄

data.ts:

import {isPlainObject} from './util';
export function transformRequest(data: any):any {
    if(isPlainObject(data)) {
        return JSON.stringify(data);
    }
    return data;
}

export function transformResponse(data: any):any {
    if(typeof data === 'string') {
        try{
            data = JSON.parse(data);
        } catch (e) {
            // do nothing;
        }
    }
    return data;
}

header.ts:

import { isPlainObject } from "./util";

function normalizeHeaderName(headers: any, normalizedName:string):void {
    if(!headers) {
        return
    }
    Object.keys(headers).forEach((name) => {
        if(name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()){
            headers[normalizedName] = headers[name];
            delete headers[name];
        }
    })
}

export function processHeaders (headers: any, data: any) :any {
    normalizeHeaderName(headers, 'Content-Type');

    if(isPlainObject(data)) {
        if(headers && headers['Content-Type']) {
            headers['Content-Type'] = 'application/json;charset=utf-8';
        }
    }

    return headers;
}

export function parseHeaders(headers: string) :any {
    let parsed = {};
    if(!headers) {
        return parsed;
    }
    headers.split('\r\n').forEach((line) => {
        let [key,val] = line.split(':');
        key = key.trim().toLowerCase();
        if(!key) {
            return;
        }
        if(val) {
            val = val.trim();
        }
        parsed[key] = val;
    })
    return parsed;
}

url.ts:

/**
 * 
 * @string url url地址
 * @any params 參數 (可選)
 */
import {isDate,isPlainObject} from './util';

function encode(val: string) : string {
    return encodeURIComponent(val)
    .replace(/%40/g,'@')
    .replace(/%3A/ig,':')
    .replace(/%24/g,'$')
    .replace(/%2C/ig,',')
    .replace(/%20/g,'+')
    .replace(/%5B/ig,'[')
    .replace(/%5D/g,']')
}

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

    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)}`);
        })
    })
    let serializedParams = parts.join('&');
    if(serializedParams) {
        const markIndex = url.indexOf('#');
        if(markIndex !== -1) {
            url = url.slice(0,markIndex);
        }
        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;

    }
    return url;
}

util.ts:

/**
 * 公共方法
 */
const toString = Object.prototype.toString;

export function isDate(val: any):val is Date {
    return toString.call(val) === '[object Date]';
}


export function isPlainObject(val: any):val is Object {
    return toString.call(val) === '[object Object]'
}

types目錄

index.ts

export type Method = 'get' | 'GET' | 'delete' | 'Delete' | 'head' | 'HEAD' | 'options' | 'OPTIONS' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH';


export interface AxiosRequestConfig {
    url: string,
    method?:Method,
    data?:any,
    params?:any,
    headers?:any,
    responseType?:XMLHttpRequestResponseType,
}

export interface AxiosResponse {
    status:number,
    statusText: string,
    headers: any,
    config: AxiosRequestConfig,
    requst:any,
    data:any,
}

export interface AxiosPromise extends Promise<AxiosResponse>{
}

根目錄

index.ts:

import {AxiosRequestConfig,AxiosPromise} from './types';
import xhr from './xhr';
import { buildURL } from './helpers/url';
import {transformRequest,transformResponse} from './helpers/data';
import { processHeaders } from './helpers/header';

function axios (config :AxiosRequestConfig) : AxiosPromise {
    processConfig(config);
    return xhr(config).then(res => {
        return transformResponseData(res);
    });
}

function processConfig(config: AxiosRequestConfig): void {
    config.url = transformURL(config);
    config.headers = transformHeaders(config);
    config.data = transformRequestData(config);
}

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

function transformRequestData (config: AxiosRequestConfig) : any {
    return transformRequest(config.data);
}

function transformHeaders(config: AxiosRequestConfig):any {
    const {headers={},data} = config;
    return processHeaders(headers,data);
}

function transformResponseData(res:AxiosPromise):AxiosPromise {
    res.data = transformResponse(res.data);
    return res;
}

export default axios;

xhr.ts:

import {AxiosRequestConfig,AxiosPromise,AxiosResponse} from './types';
import {parseHeaders} from './helpers/header';
export default function xhr(config:AxiosRequestConfig) : AxiosPromise {
    return new Promise((reslove,reject)=> {
        const {data = null,url,method='get',params,headers,responseType} = config;
        const request = new XMLHttpRequest();
        
        if(responseType) request.responseType = responseType;

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

            const responseHeaders = request.getAllResponseHeaders();
            const responseDate = responseType !== 'text' ? request.response : request.responseText;
            const response:AxiosPromise = {
                data:responseDate,
                status:request.status,
                statusText: request.statusText,
                headers: parseHeaders(responseHeaders),
                config,
                request,
            }
            reslove(response);
        }

        request.open(method.toUpperCase(),url,true);

        Object.keys(headers).forEach((name) => {
            if(data === null && name.toLocaleLowerCase() === 'content-type') {
                delete headers[name];
            }
            request.setRequestHeader(name,headers[name]);
        })

        request.send(data);
    })

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章