成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

編寫高效 TS 代碼的一些建議

開發 前端
本文阿寶哥將分享編寫高效 TS 代碼的 5 個建議,希望這些建議對大家編寫 TS 代碼能有一些幫助。

[[342931]]

本文轉載自微信公眾號「全棧修仙之路」,作者阿寶哥 。轉載本文請聯系全棧修仙之路公眾號。  

本文阿寶哥將分享編寫高效 TS 代碼的 5 個建議,希望這些建議對大家編寫 TS 代碼能有一些幫助。

一、盡量減少重復代碼

對于剛接觸 TypeScript 的小伙伴來說,在定義接口時,可能一不小心會出現以下類似的重復代碼。比如:

  1. interface Person { 
  2.   firstName: string; 
  3.   lastName: string; 
  4.  
  5. interface PersonWithBirthDate { 
  6.   firstName: string; 
  7.   lastName: string; 
  8.   birth: Date

很明顯,相對于 Person 接口來說,PersonWithBirthDate 接口只是多了一個 birth屬性,其他的屬性跟 Person 接口是一樣的。那么如何避免出現例子中的重復代碼呢?要解決這個問題,可以利用 extends 關鍵字:

  1. interface Person {  
  2.   firstName: string;  
  3.   lastName: string; 
  4.  
  5. interface PersonWithBirthDate extends Person {  
  6.   birth: Date

當然除了使用 extends 關鍵字之外,也可以使用交叉運算符(&):

  1. type PersonWithBirthDate = Person & { birth: Date }; 

另外,有時候你可能還會發現自己想要定義一個類型來匹配一個初始配置對象的「形狀」,比如:

  1. const INIT_OPTIONS = { 
  2.   width: 640, 
  3.   height: 480, 
  4.   color: "#00FF00"
  5.   label: "VGA"
  6. }; 
  7.  
  8. interface Options { 
  9.   width: number; 
  10.   height: number; 
  11.   color: string; 
  12.   label: string; 

其實,對于 Options 接口來說,你也可以使用 typeof 操作符來快速獲取配置對象的「形狀」:

  1. type Options = typeof INIT_OPTIONS; 

而在使用可辨識聯合(代數數據類型或標簽聯合類型)的過程中,也可能出現重復代碼。比如:

  1. interface SaveAction {  
  2.   type: 'save'
  3.   // ... 
  4.  
  5. interface LoadAction { 
  6.   type: 'load'
  7.   // ... 
  8.  
  9. type Action = SaveAction | LoadAction; 
  10. type ActionType = 'save' | 'load'; // Repeated types! 

為了避免重復定義 'save' 和 'load',我們可以使用成員訪問語法,來提取對象中屬性的類型:

  1. type ActionType = Action['type']; // 類型是 "save" | "load" 

然而在實際的開發過程中,重復的類型并不總是那么容易被發現。有時它們會被語法所掩蓋。比如有多個函數擁有相同的類型簽名:

  1. function get(url: string, opts: Options): Promise<Response> { /* ... */ }  
  2. function post(url: string, opts: Options): Promise<Response> { /* ... */ } 

對于上面的 get 和 post 方法,為了避免重復的代碼,你可以提取統一的類型簽名:

  1. type HTTPFunction = (url: string, opts: Options) => Promise<Response>;  
  2.  
  3. const get: HTTPFunction = (url, opts) => { /* ... */ }; 
  4. const post: HTTPFunction = (url, opts) => { /* ... */ }; 

二、使用更精確的類型替代字符串類型

假設你正在構建一個音樂集,并希望為專輯定義一個類型。這時你可以使用 interface關鍵字來定義一個 Album 類型:

  1. interface Album { 
  2.   artist: string; // 藝術家 
  3.   title: string; // 專輯標題 
  4.   releaseDate: string; // 發行日期:YYYY-MM-DD 
  5.   recordingType: string; // 錄制類型:"live" 或 "studio" 

對于 Album 類型,你希望 releaseDate 屬性值的格式為 YYYY-MM-DD,而 recordingType 屬性值的范圍為 live 或 studio。但因為接口中 releaseDate 和 recordingType 屬性的類型都是字符串,所以在使用 Album 接口時,可能會出現以下問題:

  1. const dangerous: Album = { 
  2.   artist: "Michael Jackson"
  3.   title: "Dangerous"
  4.   releaseDate: "November 31, 1991", // 與預期格式不匹配 
  5.   recordingType: "Studio", // 與預期格式不匹配 
  6. }; 

雖然 releaseDate 和 recordingType 的值與預期的格式不匹配,但此時 TypeScript 編譯器并不能發現該問題。為了解決這個問題,你應該為 releaseDate 和 recordingType 屬性定義更精確的類型,比如這樣:

  1. interface Album { 
  2.   artist: string; // 藝術家 
  3.   title: string; // 專輯標題 
  4.   releaseDate: Date; // 發行日期:YYYY-MM-DD 
  5.   recordingType: "studio" | "live"; // 錄制類型:"live" 或 "studio" 

重新定義 Album 接口之后,對于前面的賦值語句,TypeScript 編譯器就會提示以下異常信息:

  1. const dangerous: Album = { 
  2.   artist: "Michael Jackson"
  3.   title: "Dangerous"
  4.   // 不能將類型“string”分配給類型“Date”。ts(2322) 
  5.   releaseDate: "November 31, 1991", // Error 
  6.   // 不能將類型“"Studio"”分配給類型“"studio" | "live"”。ts(2322) 
  7.   recordingType: "Studio", // Error 
  8. }; 

為了解決上面的問題,你需要為 releaseDate 和 recordingType 屬性設置正確的類型,比如這樣:

  1. const dangerous: Album = { 
  2.   artist: "Michael Jackson"
  3.   title: "Dangerous"
  4.   releaseDate: new Date("1991-11-31"), 
  5.   recordingType: "studio"
  6. }; 

另一個錯誤使用字符串類型的場景是設置函數的參數類型。假設你需要寫一個函數,用于從一個對象數組中抽取某個屬性的值并保存到數組中,在 Underscore 庫中,這個操作被稱為 “pluck”。要實現該功能,你可能最先想到以下代碼:

  1. function pluck(record: any[], key: string): any[] { 
  2.   return record.map((r) => r[key]); 

對于以上的 pluck 函數并不是很好,因為它使用了 any 類型,特別是作為返回值的類型。那么如何優化 pluck 函數呢?首先,可以通過引入一個泛型參數來改善類型簽名:

  1. function pluck<T>(record: T[], key: string): any[] { 
  2.   // Element implicitly has an 'any' type because expression of type 'string' can't be used to  
  3.   // index type 'unknown'
  4.   // No index signature with a parameter of type 'string' was found on type 'unknown'.(7053) 
  5.   return record.map((r) => r[key]); // Error 

通過以上的異常信息,可知字符串類型的 key 不能被作為 unknown 類型的索引類型。要從對象上獲取某個屬性的值,你需要保證參數 key 是對象中的屬性。

說到這里相信有一些小伙伴已經想到了 keyof 操作符,它是 TypeScript 2.1 版本引入的,可用于獲取某種類型的所有鍵,其返回類型是聯合類型。接著使用 keyof 操作符來更新一下 pluck 函數:

  1. function pluck<T>(record: T[], key: keyof T) { 
  2.   return record.map((r) => r[key]); 

對于更新后的 pluck 函數,你的 IDE 將會為你自動推斷出該函數的返回類型:

  1. function pluck<T>(record: T[], key: keyof T): T[keyof T][] 

對于更新后的 pluck 函數,你可以使用前面定義的 Album 類型來測試一下:

  1. const albums: Album[] = [{ 
  2.   artist: "Michael Jackson"
  3.   title: "Dangerous"
  4.   releaseDate: new Date("1991-11-31"), 
  5.   recordingType: "studio"
  6. }]; 
  7.  
  8. // let releaseDateArr: (string | Date)[] 
  9. let releaseDateArr = pluck(albums, 'releaseDate'); 

示例中的 releaseDateArr 變量,它的類型被推斷為 (string | Date)[],很明顯這并不是你所期望的,它的正確類型應該是 Date[]。那么應該如何解決該問題呢?這時你需要引入第二個泛型參數 K,然后使用 extends 來進行約束:

  1. function pluck<T, K extends keyof T>(record: T[], key: K): T[K][] { 
  2.   return record.map((r) => r[key]); 
  3.  
  4. // let releaseDateArr: Date[] 
  5. let releaseDateArr = pluck(albums, 'releaseDate'); 

三、定義的類型總是表示有效的狀態

假設你正在構建一個允許用戶指定頁碼,然后加載并顯示該頁面對應內容的 Web 應用程序。首先,你可能會先定義 State 對象:

  1. interface State { 
  2.   pageContent: string; 
  3.   isLoading: boolean; 
  4.   errorMsg?: string; 

接著你會定義一個 renderPage 函數,用來渲染頁面:

  1. function renderPage(state: State) { 
  2.   if (state.errorMsg) { 
  3.     return `嗚嗚嗚,加載頁面出現異常了...${state.errorMsg}`; 
  4.   } else if (state.isLoading) { 
  5.     return `頁面加載中~~~`; 
  6.   } 
  7.   return `<div>${state.pageContent}</div>`; 
  8.  
  9. // 輸出結果:頁面加載中~~~ 
  10. console.log(renderPage({isLoading: true, pageContent: ""})); 
  11. // 輸出結果:<div>大家好,我是阿寶哥</div> 
  12. console.log(renderPage({isLoading: false, pageContent: "大家好,我是阿寶哥"})); 

 

創建好 renderPage 函數,你可以繼續定義一個 changePage 函數,用于根據頁碼獲取對應的頁面數據:

  1. async function changePage(state: State, newPage: string) { 
  2.   state.isLoading = true
  3.   try { 
  4.     const response = await fetch(getUrlForPage(newPage)); 
  5.     if (!response.ok) { 
  6.       throw new Error(`Unable to load ${newPage}: ${response.statusText}`); 
  7.     } 
  8.     const text = await response.text(); 
  9.     state.isLoading = false
  10.     state.pageContent = text; 
  11.   } catch (e) { 
  12.     state.errorMsg = "" + e; 
  13.   } 

對于以上的 changePage 函數,它存在以下問題:

  • 在 catch 語句中,未把 state.isLoading 的狀態設置為 false;
  • 未及時清理 state.errorMsg 的值,因此如果之前的請求失敗,那么你將繼續看到錯誤消息,而不是加載消息。

出現上述問題的原因是,前面定義的 State 類型允許同時設置 isLoading 和 errorMsg 的值,盡管這是一種無效的狀態。針對這個問題,你可以考慮引入可辨識聯合類型來定義不同的頁面請求狀態:

  1. interface RequestPending { 
  2.   state: "pending"
  3.  
  4. interface RequestError { 
  5.   state: "error"
  6.   errorMsg: string; 
  7.  
  8. interface RequestSuccess { 
  9.   state: "ok"
  10.   pageContent: string; 
  11.  
  12. type RequestState = RequestPending | RequestError | RequestSuccess; 
  13.  
  14. interface State { 
  15.   currentPage: string; 
  16.   requests: { [page: string]: RequestState }; 

在以上代碼中,通過使用可辨識聯合類型分別定義了 3 種不同的請求狀態,這樣就可以很容易的區分出不同的請求狀態,從而讓業務邏輯處理更加清晰。接下來,需要基于更新后的 State 類型,來分別更新一下前面創建的 renderPage 和 changePage 函數:

更新后的 renderPage 函數

  1. function renderPage(state: State) { 
  2.   const { currentPage } = state; 
  3.   const requestState = state.requests[currentPage]; 
  4.   switch (requestState.state) { 
  5.     case "pending"
  6.       return `頁面加載中~~~`; 
  7.     case "error"
  8.       return `嗚嗚嗚,加載第${currentPage}頁出現異常了...${requestState.errorMsg}`; 
  9.     case "ok"
  10.       `<div>第${currentPage}頁的內容:${requestState.pageContent}</div>`; 
  11.   } 

 

更新后的 changePage 函數

  1. async function changePage(state: State, newPage: string) { 
  2.   state.requests[newPage] = { state: "pending" }; 
  3.   state.currentPage = newPage; 
  4.   try { 
  5.     const response = await fetch(getUrlForPage(newPage)); 
  6.     if (!response.ok) { 
  7.       throw new Error(`無法正常加載頁面 ${newPage}: ${response.statusText}`); 
  8.     } 
  9.     const pageContent = await response.text(); 
  10.     state.requests[newPage] = { state: "ok", pageContent }; 
  11.   } catch (e) { 
  12.     state.requests[newPage] = { state: "error", errorMsg: "" + e }; 
  13.   } 

在 changePage 函數中,會根據不同的情形設置不同的請求狀態,而不同的請求狀態會包含不同的信息。這樣 renderPage 函數就可以根據統一的 state 屬性值來進行相應的處理。因此,通過使用可辨識聯合類型,讓請求的每種狀態都是有效的狀態,不會出現無效狀態的問題。

四、選擇條件類型而不是重載聲明

假設你要使用 TS 實現一個 double 函數,該函數支持 string 或 number 類型。這時,你可能已經想到了使用聯合類型和函數重載:

  1. function double(x: number | string): number | string; 
  2. function double(x: any) { 
  3.   return x + x; 

雖然這個 double 函數的聲明是正確的,但它有一點不精確:

  1. // const num: string | number 
  2. const num = double(10);  
  3. // const str: string | number 
  4. const str = double('ts'); 

對于 double 函數,你期望傳入的參數類型是 number 類型,其返回值的類型也是 number 類型。當你傳入的參數類型是 string 類型,其返回的類型也是 string 類型。而上面的 double 函數卻是返回了 string | number 類型。為了實現上述的要求,你可能想到了引入泛型變量和泛型約束:

  1. function double<T extends number | string>(x: T): T; 
  2. function double(x: any) { 
  3.   return x + x; 

在上面的 double 函數中,引入了泛型變量 T,同時使用 extends 約束了其類型范圍。

  1. // const num: 10 
  2. const num = double(10); 
  3. // const str: "ts" 
  4. const str = double('ts'); 
  5. console.log(str); 

不幸的是,我們對精確度的追求超過了預期。現在的類型有點太精確了。當傳遞一個字符串類型時,double 聲明將返回一個字符串類型,這是正確的。但是當傳遞一個字符串字面量類型時,返回的類型是相同的字符串字面量類型。這是錯誤的,因為 ts 經過 double 函數處理后,返回的是 tsts,而不是 ts。

另一種方案是提供多種類型聲明。雖然 TypeScript 只允許你編寫一個具體的實現,但它允許你編寫任意數量的類型聲明。你可以使用函數重載來改善 double 的類型:

  1. function double(x: number): number; 
  2. function double(x: string): string; 
  3. function double(x: any) { 
  4.   return x + x; 
  5.  
  6. // const num: number 
  7. const num = double(10);  
  8. // const str: string 
  9. const str = double("ts");  

很明顯此時 num 和 str 變量的類型都是正確的,但不幸的是,double 函數還有一個小問題。因為 double 函數的聲明只支持 string 或 number 類型的值,而不支持 string | number 聯合類型,比如:

  1. function doubleFn(x: number | string) { 
  2.   // Argument of type 'string | number' is not assignable to  
  3.   // parameter of type 'number'
  4.   // Argument of type 'string | number' is not assignable to  
  5.   // parameter of type 'string'
  6.   return double(x); // Error 

為什么會提示以上的錯誤呢?因為當 TypeScript 編譯器處理函數重載時,它會查找重載列表,直到找一個匹配的簽名。對于 number | string 聯合類型,很明顯是匹配失敗的。

然而對于上述的問題,雖然可以通過新增 string | number 的重載簽名來解決,但最好的方案是使用條件類型。在類型空間中,條件類型就像 if 語句一樣:

  1. function double<T extends number | string>( 
  2.   x: T 
  3. ): T extends string ? string : number; 
  4. function double(x: any) { 
  5.   return x + x; 

這與前面引入泛型版本的 double 函數聲明類似,只是它引入更復雜的返回類型。條件類型使用起來很簡單,與 JavaScript 中的三目運算符(?:)一樣的規則。T extends string ? string : number 的意思是,如果 T 類型是 string 類型的子集,則 double函數的返回值類型為 string 類型,否則為 number 類型。

在引入條件類型之后,前面的所有例子就可以正常工作了:

  1. // const num: number 
  2. const num = double(10);  
  3. // const str: string 
  4. const str = double("ts");  
  5.  
  6. // function f(x: string | number): string | number 
  7. function f(x: number | string) { 
  8.   return double(x); 

五、一次性創建對象

在 JavaScript 中可以很容易地創建一個表示二維坐標點的對象:

  1. const pt = {};  
  2. pt.x = 3;  
  3. pt.y = 4; 

然而對于同樣的代碼,在 TypeScript 中會提示以下錯誤信息:

  1. const pt = {}; 
  2. // Property 'x' does not exist on type '{}' 
  3. pt.x = 3; // Error 
  4. // Property 'y' does not exist on type '{}' 
  5. pt.y = 4; // Error 

這是因為第一行中 pt 變量的類型是根據它的值 {} 推斷出來的,你只能對已知的屬性賦值。針對這個問題,你可能會想到一種解決方案,即新聲明一個 Point 類型,然后把它作為 pt 變量的類型:

  1. interface Point { 
  2.   x: number; 
  3.   y: number; 
  4.  
  5. // Type '{}' is missing the following properties from type 'Point': x, y(2739) 
  6. const pt: Point = {}; // Error 
  7. pt.x = 3; 
  8. pt.y = 4; 

那么如何解決上述問題呢?其中一種最簡單的解決方案是一次性創建對象:

  1. const pt = {  
  2.   x: 3, 
  3.   y: 4,  
  4. }; // OK 

如果你想一步一步地創建對象,你可以使用類型斷言(as)來消除類型檢查:

  1. const pt = {} as Point;  
  2. pt.x = 3; 
  3. pt.y = 4; // OK 

但是更好的方法是一次性創建對象并顯式聲明變量的類型:

  1. const pt: Point = {  
  2.   x: 3, 
  3.   y: 4,  
  4. }; 

而當你需要從較小的對象來構建一個較大的對象時,你可能會這樣處理,比如:

  1. const pt = { x: 3, y: 4 }; 
  2. const id = { name"Pythagoras" }; 
  3. const namedPoint = {}; 
  4. Object.assign(namedPoint, pt, id); 
  5.  
  6. // Property 'id' does not exist on type '{}'.(2339) 
  7. namedPoint.name; // Error 

為了解決上述問題,你可以使用對象展開運算符 ... 來一次性構建大的對象:

  1. const namedPoint = {...pt, ...id};  
  2. namedPoint.name; // OK, type is string 

此外,你還可以使用對象展開運算符,以一種類型安全的方式逐個字段地構建對象。關鍵是在每次更新時使用一個新變量,這樣每個變量都會得到一個新類型:

  1. const pt0 = {}; 
  2. const pt1 = {...pt0, x: 3}; 
  3. const pt: Point = {...pt1, y: 4}; // OK 

雖然這是構建這樣一個簡單對象的一種迂回方式,但對于向對象添加屬性并允許 TypeScript 推斷新類型來說,這可能是一種有用的技術。要以類型安全的方式有條件地添加屬性,可以使用帶 null 或 {} 的對象展開運算符,它不會添加任何屬性:

  1. declare var hasMiddle: boolean; 
  2. const firstLast = {first'Harry'last'Truman'}; 
  3. const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})}; 

如果在編輯器中鼠標移到 president,你將看到 TypeScript 推斷出的類型:

  1. const president: { 
  2.   middle?: string; 
  3.   first: string; 
  4.   last: string; 

最終通過設置 hasMiddle 變量的值,你就可以控制 president 對象中 middle 屬性的值:

  1. declare var hasMiddle: boolean; 
  2. var hasMiddle = true
  3. const firstLast = {first'Harry'last'Truman'}; 
  4. const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})}; 
  5.  
  6. let mid = president.middle 
  7. console.log(mid); // S 

六、參考資源

 

effective-typescript-specific-ways-improve

 

責任編輯:武曉燕 來源: 全棧修仙之路
相關推薦

2011-11-25 10:35:20

Java

2017-11-29 18:52:13

Python新手編碼建議

2012-11-09 10:46:24

Canonical

2018-11-20 14:24:46

數據分析數據庫統計

2020-04-08 10:21:58

bash腳本語言

2020-04-14 09:22:47

bash腳本技巧

2022-04-14 10:22:44

故事卡業務

2015-08-26 09:31:26

程序員建議

2023-11-10 08:48:09

Lombok庫Java8

2015-08-26 08:31:35

核心程序員成長

2011-04-27 09:21:09

程序員

2016-11-11 20:33:53

Hadoop大數據云計算

2017-05-02 21:08:35

開發架構工程師

2010-01-15 10:34:59

Linux命令行操作

2013-12-03 10:30:28

iOS開發程序員自我提升

2021-09-27 10:04:03

Go程序處理

2009-06-30 20:44:44

2013-04-28 09:44:44

2020-05-19 08:06:57

代碼重構代碼開發

2018-06-21 15:23:36

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 不用播放器看的av | 国产99久久久国产精品 | 91久久综合亚洲鲁鲁五月天 | 中文字幕高清一区 | 欧美爱爱视频网站 | 久久久久久久一区二区三区 | 日韩在线不卡视频 | 亚洲精品久久久久久一区二区 | 国产二区在线播放 | 欧美久久电影 | 午夜小视频免费观看 | 精品国产一区一区二区三亚瑟 | 中文字幕av色 | 国产一级免费视频 | 一区二区精品在线 | 日中文字幕在线 | 成人国产在线视频 | 国产精品久久毛片av大全日韩 | 国产精品一区在线 | 亚洲乱码国产乱码精品精98午夜 | 日日夜夜天天综合 | 久久99精品国产99久久6男男 | 国产欧美一区二区三区久久 | 国产精品久久久久久久久免费丝袜 | h在线看| 日韩一区二区三区在线播放 | 97国产精品视频人人做人人爱 | 精品国产欧美一区二区三区成人 | 91久久综合 | 中文字幕国产视频 | 日本不卡一区 | 精品国产免费一区二区三区五区 | 91在线一区| www一级片 | 九九精品在线 | 懂色中文一区二区在线播放 | 欧美黑人又粗大 | 国产精品毛片一区二区在线看 | 中文字幕精品一区二区三区精品 | 亚洲精品久久久一区二区三区 | 激情国产|