赖同学


  • 首页

  • 标签

  • 分类

  • 归档

  • 站点地图

  • 留言

  • 搜索

基于ts重构axios

发表于 September 3, 2019|分类于 TypeScript|阅读次数: –
字数统计: 1345|阅读时间: 7 min

基于ts重构axios

ustbhuangyi 老师的 基于TypeScript从零重构axios学习记录。

知识点

TypeScript 常用语法:

基础类型 、 函数 、 变量声明 、 接口 、 类 、 泛型 、 类型推新 、 高级类型

axios js库:

项目脚手架 、 基础功能实现 、 异常情况处理 、 接口扩展 、 拦截器实现 、 配置化实现 、 取消功能实现 、 其他功能实现等等

主要工具: Jest 、 TSLint 、 Commitizen 、 Prettier 、 RollupJS 、 Semantic release

基本语法

点我

需求分析

Features

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

基于 XMLHttpRequest 编写基本请求代码

处理请求数据:url/body/headers

src/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;
}

src/xhr.ts

import { AxiosRequestConfig } from './types';

export default function xhr(config: AxiosRequestConfig): void {
  const { data = null, url, method = 'get', headers } = config;
  const request = new XMLHttpRequest();
  // method,url,async
  request.open(method.toUpperCase(), url, true);
  Object.keys(headers).forEach(name => {
    if (data === null && name.toLowerCase() === 'content-type') {
      delete headers[name];
    } else {
      request.setRequestHeader(name, headers[name]);
    }
  });
  request.send(data);
}

src/index.ts

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

function axios(config: AxiosRequestConfig): void {
  processConfig(config);
  xhr(config);
}

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

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

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

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

export default axios;

工具类

data.ts

import { isPlainObject } from './util';

export function transformRequest(data: any): any {
  if (isPlainObject(data)) {
    return JSON.stringify(data);
  }
  return data;
}

headers.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;
}

url.ts

import { isDate, isPlainObject } from './util'

function encode(val: string): string {
  return encodeURIComponent(val)
    .replace(/%40/g, '@')
    .replace(/%3A/ig, ':')
    .replace(/%24/g, '**util.ts**

​```tsx
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

export interface AxiosResponse {
  data: any;
  status: number;
  statusText: string;
  headers: any;
  config: AxiosRequestConfig;
  request: any;
}
export interface AxiosPromise extends Promise<AxiosResponse> {}

处理 headers 的数据

helpers/header.ts

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 = Object.create(null);
  if (!headers) {
    return headers;
  }
  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;
}

处理 响应data

helpers/data.ts

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

修改 xhr, 返回一个 Promise

xhr.ts

import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types';
import { parseHeaders } from './helpers/headers';

export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  return new Promise(resolve => {
    const { data = null, url, method = 'get', headers, responseType } = config;
    const request = new XMLHttpRequest();
    if (responseType) {
      request.responseType = responseType;
    }
    // method,url,async
    request.open(method.toUpperCase(), url, true);

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

      const responseHeaders = parseHeaders(request.getAllResponseHeaders());
      const responseData = responseType && 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);
  });
}

具体代码地址

)

.replace(/%2C/ig, ',')
.replace(/%20/g, '+')
.replace(/%5B/ig, '[')
.replace(/%5D/ig, ']')

}

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**

​```tsx
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

export interface AxiosResponse {
  data: any;
  status: number;
  statusText: string;
  headers: any;
  config: AxiosRequestConfig;
  request: any;
}
export interface AxiosPromise extends Promise<AxiosResponse> {}

处理 headers 的数据

helpers/header.ts

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 = Object.create(null);
  if (!headers) {
    return headers;
  }
  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;
}

处理 响应data

helpers/data.ts

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

修改 xhr, 返回一个 Promise

xhr.ts

import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from './types';
import { parseHeaders } from './helpers/headers';

export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  return new Promise(resolve => {
    const { data = null, url, method = 'get', headers, responseType } = config;
    const request = new XMLHttpRequest();
    if (responseType) {
      request.responseType = responseType;
    }
    // method,url,async
    request.open(method.toUpperCase(), url, true);

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

      const responseHeaders = parseHeaders(request.getAllResponseHeaders());
      const responseData = responseType && 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);
  });
}

具体代码地址

TypeScript
react hook实践
前端必备的测试
  • 文章目录
  • 站点概览
  1. 1.基于ts重构axios
    1. 1.基本语法
    2. 2.需求分析
      1. 1.Features
    3. 3.基于 XMLHttpRequest 编写基本请求代码
      1. 1.处理请求数据:url/body/headers
        1. 1.工具类
      2. 2.处理响应数据
      3. 3.处理响应数据
© 2018 — 2025赖彬鸿
载入天数...载入时分秒...
0%