拒絕作一個只會用 API 的文檔工程師,本文將會讓你從重複造輪子的過程當中掌握 web 開發相關的基本知識,特別是 XMLHttpRequest。html
又是一篇關於 TypeScript 的分享,年末了,請容許我沉澱一下。上次用 TypeScript 重構 Vconsole 的項目 埋下了對 Axios 源碼解析的梗。因而,此次分享的主題就是 如何從零用 TypeScript 重構 Axios 以及爲何我要這麼作。前端
筆者在用 TypeScript 重複造輪子的時候目的仍是很明確的,不只是爲了用 TypeScript 養成一種好的開發習慣,更重要的是瞭解工具庫關聯的基礎知識。 只有更多地注重基礎知識,才能早日擺脫文檔工程師的困擾。(Ps: 用 TypeScript,也是爲了擺脫前端查文檔的宿命!)node
本次分享包括如下內容:react
項目源碼,分享可能會錯過某些細節實現,須要的能夠看源碼,測試用例基本跑通了。想一想,5w star 的庫,就這樣本身實現了一遍。ios
Axios 是什麼?git
Promise based HTTP client for the browser and node.js
axios 是基於 Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端,它自己具備如下特性 ( √ 表示本項目具有該特性 ):github
/src/core/dispatchRequest.ts
/src/core/dispatchRequest.ts
這裏主要講解瀏覽器端的 XHR 實現,限於篇幅不會涉及 node 下的 http 。若是你願意一層一層瞭解它,你會發現實現 axios 仍是很簡單的,來一塊兒探索吧!web
首先來看下目錄。chrome
目錄與 Axios 基本保持一致,core 是 Axios
類的核心代碼。adapters 是 XHR 核心實現,Cancel 是與 取消請求相關的代碼。helpers 用於放經常使用的工具函數。Karma.conf.js
及 test 目錄與單元測試相關。.travis.yml
用於配置 在線持續集成,另外可在 github 的 README 文件配置構建狀況。typescript
打包工具選用的是 Parcel,目的是零配置編譯 TypeScript 。入口文件爲 src 目錄下的 index.html
,只需在 入口文件裏引入 index.ts
便可完成熱更新,TypeScript 編譯等配置:
<body> <script src="index.ts"></script> </body>
Parcel 相關:
# 全局安裝 yarn global add parcel-bundler # 啓動服務 parcel ./src/index.html # 打包 parcel build ./src/index.ts
運行完 parcel 命令會啓動一個本地服務器,能夠經過 .vscode
目錄下的 launch.json
配置 Vscode 調試工具。
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Lanzar Chrome contra localhost", "url": "http://localhost:1234", "webRoot": "${workspaceRoot}", "sourceMaps": true, "breakOnLoad": true, "sourceMapPathOverrides": { "../*": "${webRoot}/*" } } ] }
配置完成後,可斷點調試,按 F5 便可開始調試。
TypeScript 總體配置和規範檢測參考以下:
強烈建議開啓 tslint
,安裝 vscode tslint 插件 並在 .vscode
目錄下的 .setting
配置以下格式:
{ "editor.tabSize": 2, "editor.rulers": [120], "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "files.exclude": { "**/.git": true, "**/.DS_Store": true }, "eslint.enable": false, "tslint.autoFixOnSave": true, "typescript.format.enable": true, "typescript.tsdk": "node_modules/typescript/lib" }
若是有安裝 Prettier需注意二者風格衝突,不管格式化代碼的插件是什麼,咱們的目的只有一個,就是 保證代碼格式化風格統一。( 最好遵循 lint 規範 )。
ps:.vscode
目錄可隨 git 跟蹤進版本管理,這樣可讓 clone 倉庫的使用者更友好。
另外能夠經過,vscode 的 控制面板中的問題 tab 迅速查看當前項目問題所在。
咱們時常會有想要編輯某段測試代碼,又不想在項目裏編寫的需求(好比用 TypeScript 寫一個 deepCopy 函數),不想脫離 vscode 編輯器的話,推薦使用 quokka,一款可當即執行腳本的插件。
接着像這樣
({ plugins: 'jsdom-quokka-plugin', jsdom: { html: `<div id="test">Hello</div>` } }); const testDiv = document.getElementById('test'); console.log(testDiv.innerHTML);
重構的思路首先是看文檔提供的 API,或者 index.d.ts
聲明文件。 優秀一點的源碼能夠看它的測試用例,通常會提供 API 相關的測試,如 Axios API 測試用例 ,本次分享實現 API 以下:
總得下來就是五類 API,比葫蘆娃還少。有信心了吧,咱們來一個個"送人頭"。
這些 API 能夠統稱爲實例方法,有實例,就確定有類。因此在講 API 實現以前,先讓咱們來看一下 Axios 類。
兩個屬性(defaults,interceptors),一個通用方法( request ,其他的方法如,get、post、等都是基於 request,只是參數不一樣 )真的不能再簡單了。
export default class Axios { defaults: AxiosRequestConfig; interceptors: { request: InterceptorManager; response: InterceptorManager; }; request(config: AxiosRequestConfig = {}) { // 請求相關 } // 由 request 延伸出 get 、post 等 }
Axios 庫默認導出的是 Axios 的一個實例 axios,而不是 Axios 類自己。可是,這裏並無直接返回 Axios 的實例,而是將 Axios 實例方法 request 的上下文設置爲了 Axios。 因此 axios 的類型是 function,不是 object。但因爲 function 也是 Object 因此能夠設置屬性和方法。因而 axios 既能夠表現的像實例,又能夠直接函數調用 axios(config)
。具體實現以下:
const createInstance = (defaultConfig: AxiosRequestConfig) => { const context = new Axios(defaultConfig); const instance = Axios.prototype.request.bind(context); extend(instance, Axios.prototype, context); extend(instance, context); return instance; }; axios.create = (instanceConfig: AxiosRequestConfig) => { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; const axios: AxiosExport = createInstance(defaults); axios.Axios = Axios; export default axios;
axios 還提供了一個 Axios 類的屬性,可供別的類繼承。另外暴露了一個工廠函數,接收一個配置項參數,方便使用者建立多個不一樣配置的請求實例。
若是不看源碼,咱們用一個類,最關心的應該是構造函數,默認設置了什麼屬性,以及咱們能夠修改哪些屬性。體如今 Axios 就是,請求的默認配置。
下面咱們來看下默認配置:
const defaults: AxiosRequestConfig = { headers: headers(), // 請求頭 adapter: getDefaultAdapter(), // XMLHttpRequest 發送請求的具體實現 transformRequest: transformRequest(), // 自定義處理請求相關數據,默認有提供一個修改根據請求的 data 修改 content-type 的方法。 transformResponse: transformResponse(), // 自定義處理響應相關數據,默認提供了一個將 respone 數據轉換爲 JSON格式的方法 timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', validateStatus(status: number) { return status >= 200 && status < 300; } };
也就是說,若是你用 Axios ,你應該知道它有哪些默認設置。
先來看下 axios 接受的請求參數都有哪些屬性,如下參數屬性均是可選的。使用 TypeScript 事先定義了這些參數的類型,接下來傳參的時候就能夠檢驗傳參的類型是否正確。
export interface AxiosRequestConfig { url?: string; // 請求連接 method?: string; // 請求方法 baseURL?: string; // 請求的基礎連接 xsrfCookieName?: string; // CSRF 相關 xsrfHeaderName?: string; // CSRF 相關 headers?: any; // 請求頭設置 params?: any; // 請求參數 data?: any; // 請求體 timeout?: number; // 超時設置 withCredentials?: boolean; // CSRF 相關 responseType?: XMLHttpRequestResponseType; // 響應類型 paramsSerializer?: (params: any) => string; // url query 參數格式化方法 onUploadProgress?: (progressEvent: any) => void; // 上傳處理函數 onDownloadProgress?: (progressEvent: any) => void; // 下載處理函數 validateStatus?: (status: number) => boolean; adapter?: AxiosAdapter; auth?: any; transformRequest?: AxiosTransformer | AxiosTransformer[]; transformResponse?: AxiosTransformer | AxiosTransformer[]; cancelToken?: CancelToken; }
export interface AxiosRequestConfig { url?: string; // 請求連接 method?: string; // 請求方法 baseURL?: string; // 請求的基礎連接 }
先來看下相關知識:
url,method 做爲 XMLHttpRequest 中 open 方法的參數。
open 語法:
xhrReq.open(method, url, async, user, password);
url 是一個 DOMString,表示發送請求的 URL。
注意:將 null | undefined 傳遞給接受 DOMString 的方法或參數時一般會把其 stringifies 爲 「null」 | 「undefined」
用原生的 open 方法傳遞以下參數,實際請求 URL 以下:
let xhr = new XMLHttpRequest(); // 假設當前 window.location.host 爲 http://localhost:1234 xhr.open('get', ''); // http://localhost:1234/ xhr.open('get', '/'); // href http://localhost:1234/ xhr.open('get', null); // http://localhost:1234/null xhr.open('get', undefined); // http://localhost:1234/undefined
能夠看到默認 baseURL 爲 window.location.host
相似 http://localhost:1234/undefined
這種 URL 請求成功的狀況是存在的。當前端動態傳遞 url 參數時,參數是有可能爲 null
或 undefined
,若是不是經過 response 的狀態碼來響應操做,此時獲得的結果就跟預想的不同。這讓我想起了,JavaScript 隱式轉換的坑,比比皆是。(此處安利 TypeScript 和 '===' 操做符)
對於這種狀況,使用 TypeScript 能夠在開發階段規避這些問題。但若是是動態賦值(好比請求返回的結果做爲 url 參數時),須要給值判斷下類型,必要時可拋出錯誤或轉換爲其餘想要的值。
接着來看下 axios url 相關,主要提供了 baseURL 的支持,能夠經過 axios.defaults.baseURL
或 axios({baseURL:'...'})
const isAbsoluteURL = (url: string): boolean => { // 一、判斷是否爲協議形式好比 http:// return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); }; const combineURLs = (baseURL: string, relativeURL: string): string => { return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; }; const suportBaseURL = () => { // 二、baseURL 處理 return baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url; };
在 axios 中 發送請求時 params 和 data 的區別在於:
axios 對 params 的處理分爲賦值和序列化(用戶可自定義 paramsSerializer 函數)
helpers 目錄下的 buildURL
文件主要生成完整的 URL 請求地址。
XMLHttpRequest 是經過 send 方法把 data 添加到請求體的。
語法以下:
send(); send(ArrayBuffer data); send(ArrayBufferView data); send(Blob data); send(Document data); send(DOMString? data); send(FormData data);
能夠看到 data 有這幾種類型:
但願瞭解 data 有哪些類型的能夠看這篇
實際使用:
var xhr = new XMLHttpRequest(); xhr.open('GET', '/server', true); xhr.onload = function() { // 請求結束後,在此處寫處理代碼 }; xhr.send(null); // xhr.send('string'); // xhr.send(new Blob()); // xhr.send(new Int8Array()); // xhr.send({ form: 'data' }); // xhr.send(document);
另外,在發送請求即調用 send()方法以前應該根據 data 類型使用 setRequestHeader() 方法設置 Content-Type 頭部來指定數據流的 MIME 類型。
Axios 在 transformRequest
配置項裏有個默認的方法用於修改請求( 可自定義 )。
const transformRequest = () => { return [ (data: any, headers: any) => { // ...根據 data 類型修改對應 headers } ]; };
axios 提供配置 HTTP 請求的方法:
export interface AxiosRequestConfig { method?: string; }
可選配置以下:
接着瞭解下 HTTP 請求
HTTP 定義了一組請求方法, 以代表要對給定資源執行的操做。指示針對給定資源要執行的指望動做. 雖然他們也能夠是名詞, 但這些請求方法有時被稱爲 HTTP 動詞. 每個請求方法都實現了不一樣的語義, 但一些共同的特徵由一組共享:: 例如一個請求方法能夠是 safe, idempotent, 或 cacheable.
篇幅有限,看 MDN
axios 提供配置 HTTP 請求頭的方法:
export interface AxiosRequestConfig { headers?: any; }
一個請求頭由名稱(不區分大小寫)後跟一個冒號「:」,冒號後跟具體的值(不帶換行符)組成。該值前面的引導空白會被忽略。
請求頭能夠被定義爲:被用於 http 請求中而且和請求主體無關的那一類 HTTP header。某些請求頭如Accept
,Accept-*
,If-*
`容許執行條件請求。某些請求頭如:Cookie,
User-Agent和
Referer` 描述了請求自己以確保服務端能返回正確的響應。
並不是全部出如今請求中的 http 首部都屬於請求頭,例如在 POST 請求中常常出現的 Content-Length
其實是一個表明請求主體大小的 entity header,雖然你也能夠把它叫作請求頭。
axios 根據請求方法 設置了不一樣的 Content-Type
和 Accpect
請求頭。
XMLHttpRequest 對象提供的 XMLHttpRequest對象提供的.setRequestHeader()
方法爲開發者提供了一個操做這兩種頭部信息的方法,並容許開發者自定義請求頭的頭部信息。
XMLHttpRequest.setRequestHeader() 是設置 HTTP 請求頭部的方法。此方法必須在 open() 方法和 send() 之間調用。若是屢次對同一個請求頭賦值,只會生成一個合併了多個值的請求頭。
若是沒有設置 Accept 屬性,則此發送出 send() 的值爲此屬性的默認值/ 。
安全起見,有些請求頭的值只能由 user agent 設置:forbidden header names 和 forbidden response header names.
默認狀況下,當發送 AJAX 請求時,會附帶如下頭部信息:
axios 設置代碼以下:
// 在 adapters 目錄下的 xhr.ts 文件中: if ('setRequestHeader' in requestHeaders) { // 經過 XHR 的 setRequestHeader 方法設置請求頭信息 for (const key in requestHeaders) { if (requestHeaders.hasOwnProperty(key)) { const val = requestHeaders[key]; if ( typeof requestData === 'undefined' && key.toLowerCase() === 'content-type' ) { delete requestHeaders[key]; } else { request.setRequestHeader(key, val); } } } }
至於能不能修改 http header,個人建議是固然不能隨便修改任何字段。
connection
,cache-control
等。不會影響你的正常訪問,但有可能會慢一點。只要是用戶主動輸入網址訪問時發送的 http 請求,那這些頭部字段都是瀏覽器自動生成的,好比 host,cookie,user-agent, Accept-Encoding 等。JS 可以控制瀏覽器發起請求,也能在這裏增長一些 header,可是考慮到安全和性能的緣由,對 JS 控制 header 的能力作了一些限制,好比 host 和 cookie, user-agent 等這些字段,JS 是沒法干預的禁止修改的消息首部。關於 HTTP 的知識實在多,這裏簡單談到相關聯的知識。這裏埋下伏筆,後續如有更適合講 HTTP 的例子,再延伸。
接下來的 CSRF,就會修改 headers。
與 CSRF 相關的配置屬性有這三個:
export interface AxiosRequestConfig { xsrfCookieName?: string xsrfHeaderName?: string withCredentials?: boolean; } // 默認配置爲 { xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', withCredentials: false }
那麼,先來簡單瞭解 CSRF
跨站請求僞造(英語:Cross-site request forgery),也被稱爲 one-click attack 或者 session riding,一般縮寫爲 CSRF 或者 XSRF, 是一種挾制用戶在當前已登陸的 Web 應用程序上執行非本意的操做的攻擊方法。跟 跨網站腳本(XSS)相比,XSS 利用的是用戶對指定網站的信任,CSRF 利用的是網站對用戶網頁瀏覽器的信任。
你這能夠這麼理解 CSRF 攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。CSRF 可以作的事情包括:以你名義發送郵件,發消息,盜取你的帳號,甚至於購買商品,虛擬貨幣轉帳。形成的問題包括:我的隱私泄露以及財產安全。
在他們的釣魚站點,攻擊者能夠經過建立一個 AJAX 按鈕或者表單來針對你的網站建立一個請求:
<form action="https://my.site.com/me/something-destructive" method="POST"> <button type="submit">Click here for free money!</button> </form>
要完成一次 CSRF 攻擊,受害者必須依次完成兩個步驟:
1.登陸受信任網站 A,並在本地生成 Cookie。
2.在不登出 A 的狀況下,訪問危險網站 B。
使用 JavaScript 發起 AJAX 請求是限制跨域的。 不能經過一個簡單的 <form>
來發送 JSON
, 因此,經過只接收 JSON,你能夠下降發生上面那種狀況的可能性。
第一種減輕 CSRF 攻擊的方法是禁用 cross-origin requests(跨域請求)。若是你但願容許跨域請求,那麼請只容許 OPTIONS, HEAD, GET
方法,由於他們沒有反作用。不幸的是,這不會阻止上面的請求因爲它沒有使用 JavaScript(所以 CORS 不適用)。
HTTP 頭中有一個 Referer 字段,這個字段用以標明請求來源於哪一個地址。在處理敏感數據請求時,一般來講,Referer 字段應和請求的地址位於同一域名下。這種辦法簡單易行,工做量低,僅須要在關鍵訪問處增長一步校驗。但這種辦法也有其侷限性,因其徹底依賴瀏覽器發送正確的 Referer 字段。雖然 http 協議對此字段的內容有明確的規定,但並沒有法保證來訪的瀏覽器的具體實現,亦沒法保證瀏覽器沒有安全漏洞影響到此字段。而且也存在攻擊者攻擊某些瀏覽器,篡改其 Referer 字段的可能。(PS:可見遵循 web 標準多麼重要)
最終的解決辦法是使用 CSRF tokens。 CSRF tokens 是如何工做的呢?
攻擊者須要經過某種手段獲取你站點的 CSRF token, 他們只能使用 JavaScript 來作。 因此,若是你的站點不支持 CORS, 那麼他們就沒有辦法來獲取 CSRF token, 下降了威脅。
確保 CSRF token 不能經過 AJAX 訪問到!
不要建立一個/CSRF
路由來獲取一個 token, 尤爲不要在這個路由上支持 CORS!
token 須要是不容易被猜到的, 讓它很難被攻擊者嘗試幾回獲得。 它不須要是密碼安全的。 攻擊來自從一個未知的用戶的一次或者兩次的點擊, 而不是來自一臺服務器的暴力攻擊。
這裏有個 withCredentials
,先來了解下。
XMLHttpRequest.withCredentials 屬性是一個 Boolean 類型,它指示了是否該使用相似 cookies,authorization headers(頭部受權)或者 TLS 客戶端證書這一類資格證書來建立一個跨站點訪問控制(cross-site Access-Control)請求。在同一個站點下使用 withCredentials 屬性是無效的。若是在發送來自其餘域的 XMLHttpRequest 請求以前,未設置 withCredentials 爲 true,那麼就不能爲它本身的域設置 cookie 值。而經過設置 withCredentials 爲 true 得到的第三方 cookies,將會依舊享受同源策略,所以不能被經過 document.cookie 或者從頭部相應請求的腳本等訪問。
// 在標準瀏覽器環境下 (非 web worker 或者 react-native) 則添加 xsrf 頭 if (isStandardBrowserEnv()) { // 必須在 withCredentials 或 同源的狀況,才設置 xsrfHeader 頭 const xsrfValue = (withCredentials || isURLSameOrigin(url)) && xsrfCookieName ? cookies.read(xsrfCookieName) : undefined; if (xsrfValue && xsrfHeaderName) { requestHeaders[xsrfHeaderName] = xsrfValue; } }
對於 CSRF,須要讓後端同窗,敏感的請求不要使用相似 get 這種冪等的,可是因爲 Form 表單發起的 POST 請求並不受 CORS 的限制,所以能夠任意地使用其餘域的 Cookie 向其餘域發送 POST 請求,造成 CSRF 攻擊。
這時,若是有涉及敏感信息的請求,須要跟後端同窗配合,進行 XSRF-Token 認證。此時,咱們用 axios 請求的時候,就能夠經過設置 XMLHttpRequest.withCredentials=true
以及設置 axios({xsrfCookieName:'',xsrfHeaderName:''})
,不使用則會用默認的 XSRF-TOKEN
和 X-XSRF-TOKEN
(拿這個跟後端配合便可)。
因此,axios 特性中,客戶端支持防止 CSRF/XSRF。只是方便設置 CORF-TOKEN ,關鍵仍是要後端同窗的接口支持。(PS:先後端相親相愛多重要,因此做爲前端的咱們仍是儘量多瞭解這方面的知識)
axios 經過適配器模式,提供了支持 node.js 的 http 以及客戶端的 XMLHttpRequest 的兩張實現,本文主要講解 XHR 實現。
大概的實現邏輯以下:
const xhrAdapter = (config: AxiosRequestConfig): AxiosPromise => { return new Promise((resolve, reject) => { let request: XMLHttpRequest | null = new XMLHttpRequest(); setHeaders(); openXHR(); setXHR(); sendXHR(); }); };
若是逐行講解,不如錄個教程視頻,建議你們直接看 adapters 目錄下的 xhr.ts
,在關鍵地方都有註釋!
data
,auth
,xsrfHeaderName
設置對應的 headerssetXHR
主要是在 request.readyState === 4
的時候對響應數據做處理以及錯誤處理XMLHttpRequest.send
方法返回的是一個 Promise 對象,因此支持 Promise 的全部特性。
請求攔截在 axios 應該算是一個比較騷的操做,實現很是簡單。有點像一系列按順序執行的 Promise。
直接看代碼實現:
// interceptors 分爲 request 和 response。 interface interceptors { request: InterceptorManager; response: InterceptorManager; } request (config: AxiosRequestConfig = {}) { const { method } = config const newConfig: AxiosRequestConfig = { ...this.defaults, ...config, method: method ? method.toLowerCase() : 'get' } // 攔截器原理:[請求攔截器,發送請求,響應攔截器] 順序執行 // 一、創建一個存放 [ resolve , reject ] 的數組, // 這裏若是沒有攔截器,則執行發送請求的操做。 // 因爲以後都是 resolve 和 reject 的組合,因此這裏默認 undefined。真是騷操做! const chain = [ dispatchRequest, undefined ] // 二、Promise 成功後會往下傳遞參數,因而這裏先傳入合併後的參數,供以後的攔截器使用 (若是有的話)。 let promise: any = Promise.resolve(newConfig) // 三、又是一波騷操做,完美的運用了數組的方法。咋不用 reduce 實現 promise 順序執行呢 ? // request 請求攔截器確定須要 `dispatchRequest` 在前面,因而 [interceptor.fulfilled, interceptor.rejected, dispatchRequest, undefined] this.interceptors.request.forEach((interceptor: Interceptor) => { chain.unshift(interceptor.fulfilled, interceptor.rejected) }) // response 響應攔截器確定須要在 `dispatchRequest` 後面,因而 [dispatchRequest, undefined,interceptor.fulfilled, interceptor.rejected] this.interceptors.response.forEach((interceptor: Interceptor) => { chain.push(interceptor.fulfilled, interceptor.rejected) }) // 四、依次執行 Promise( fulfilled,rejected ) while (chain.length) { promise = promise.then(chain.shift(), chain.shift()) } return promise }
又是對基礎知識的完美運用,不管是 Promise 仍是數組的變異方法都算巧妙運用。
固然,Promise 的順序執行還能夠這樣:
function sequenceTasks(tasks) { function recordValue(results, value) { results.push(value); return results; } var pushValue = recordValue.bind(null, []); return tasks.reduce(function(promise, task) { return promise.then(task).then(pushValue); }, Promise.resolve()); }
若是不知道 XMLHttpRequest 有 absort 方法,確定會以爲取消請求這種秀操做的怎麼可能呢!( PS:基礎知識多重要 )
const { cancelToken } = config; const request = new XMLHttpRequest(); if (cancelToken) { cancelToken.promise .then(cancel => { if (!request) { return; } request.abort(); reject(cancel); request = null; }) .catch(err => { console.error(err); }); }
至於 CancelToken
就不講了,好奇怪的實現。沒有感悟到原做者的設計真諦!
最後到了單元測試的環節,先來看下相關依賴。
用的是 karma,配置以下:
執行命令:
yarn test
本項目是基於 jasmine
來寫測試用例,仍是比較簡單的。
karma 會跑 test 目錄下的全部測試用例,感受測試用例用 TypeScript 來寫,有點難受。由於測試原本就是要讓參數多樣化,然而 TypeScript 事先規定了數據類型。雖然可使用泛型來解決,可是總以爲有點變扭。
不過,整個測試用例跑下來,代碼強壯了不少。對於這種庫來講,仍是頗有必要的。若是須要二次重構,基於 TypeScript 和 有覆蓋大部分函數的單元測試支持,應該會容易不少。
感謝能看到這裏的朋友,想必也是 TypeScript 或 Axios 的粉絲,不妨相互認識一下。
仍是那句話,TypeScript 確實好用。短期內就能將 Axios 大體重構了一遍,感興趣的能夠跟着一塊兒。老規矩,在分享中不會具體講庫怎麼用 (想必,若是本身擼完這麼一個項目,應該不用去看 API 了吧。) ,更多的是從廣度拓展你們的知識點。若是對某個關鍵詞比較陌生,這就是進步的時候了。好比筆者接下來要去深刻涉略 HTTP 了。雖然,感受目前 TypeScript 的熱度好像好不是很高。好東西,老是那些不容易變的。哈,別到時候打臉了。
我變強了嗎? 不扯了,聽楊宗緯的 "我變了,我沒變" 了。
切記,沒有什麼是看源碼解決不了的 bug。