這一次主要是基礎功能實現
目錄結構:僅僅包含 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);
})
}